Remove Redux from Toaster component (treejs#6) + field visual update
Change-Id: I3aaab206f6d78a628d2e4b1e9ea0422fa39af589
This commit is contained in:
parent
5f2ad09b39
commit
23daaab869
26 changed files with 321 additions and 359 deletions
|
@ -1,9 +1,10 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { STATUS } from '@treejs/types/common';
|
||||
import { IHTMLElement } from '@treejs/types/form';
|
||||
|
||||
import Button from '../Button';
|
||||
import CheckSquareIcon from '../Icons/CheckSquare';
|
||||
import SquareIcon from '../Icons/Square';
|
||||
|
||||
|
@ -24,7 +25,7 @@ const Checkbox: React.FC<ICheckboxFieldProps> = (props) => {
|
|||
}, [props.checked]);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLLabelElement>): void => {
|
||||
(e: React.MouseEvent<HTMLElement>): void => {
|
||||
if (props.onChange) {
|
||||
props.onChange(!checked, e);
|
||||
}
|
||||
|
@ -42,23 +43,24 @@ const Checkbox: React.FC<ICheckboxFieldProps> = (props) => {
|
|||
[checked]
|
||||
);
|
||||
|
||||
const boxClassName = clsx('text-xl field-checkbox', {
|
||||
'field-checkbox--info': status === STATUS.info,
|
||||
'field-checkbox--warning': status === STATUS.warning,
|
||||
'field-checkbox--error': status === STATUS.error,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="checkbox-container">
|
||||
<label className="cursor-pointer" htmlFor={name} onClick={handleClick}>
|
||||
<div className="block text-black pb-2 field-label">{title}</div>
|
||||
<Button
|
||||
label={
|
||||
<div className="flex justify-between items-center">
|
||||
{checked ? <CheckSquareIcon fontSize={20} /> : <SquareIcon fontSize={20} />}
|
||||
<b className="ml-2">{label}</b>
|
||||
</div>
|
||||
}
|
||||
className="m-0 normal-case w-auto font-thin inline"
|
||||
status={status}
|
||||
pressed={checked}
|
||||
/>
|
||||
<div className="field-container checkbox-container">
|
||||
<input id={name} name={name} className="hidden field-input" type="checkbox" onBlur={handleBlur} />
|
||||
<label className="field-label" htmlFor={name} onClick={handleClick}>
|
||||
{label}
|
||||
</label>
|
||||
<input id={name} name={name} className="hidden" type="checkbox" onBlur={handleBlur} />
|
||||
<div className="flex justify-between items-center ml-2 mr-1" onClick={handleClick}>
|
||||
{checked ? <CheckSquareIcon className={boxClassName} /> : <SquareIcon className={boxClassName} />}
|
||||
</div>
|
||||
<div className="cursor-pointer" onClick={handleClick}>
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,17 +12,15 @@ import InfoCircleIcon from '../Icons/InfoCircle';
|
|||
|
||||
type IProps = {
|
||||
children?: React.ReactElement | string;
|
||||
filled?: boolean;
|
||||
status: STATUS;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
function Message(props: IProps) {
|
||||
const { status = STATUS.none, title, children, filled = false } = props;
|
||||
const { status = STATUS.none, title, children } = props;
|
||||
return (
|
||||
<div
|
||||
className={clsx('message', {
|
||||
'message--filled': filled,
|
||||
'message--none': status === STATUS.none,
|
||||
'message--info': status === STATUS.info,
|
||||
'message--success': status === STATUS.success,
|
||||
|
@ -30,14 +28,14 @@ function Message(props: IProps) {
|
|||
'message--error': status === STATUS.error,
|
||||
})}
|
||||
>
|
||||
<div className="inline-block align-middle mr-4 text-xl">
|
||||
<div className="inline-block align-middle mr-4 text-3xl">
|
||||
{status === STATUS.info && <InfoCircleIcon />}
|
||||
{status === STATUS.success && <CheckCircleIcon />}
|
||||
{status === STATUS.warning && <ExclamationTriangleFillIcon />}
|
||||
{status === STATUS.error && <ExclamationCircleIcon />}
|
||||
{status === STATUS.none && <HappyMessageIcon />}
|
||||
</div>
|
||||
<div className="text-xl font-normal max-w-full flex-initial">
|
||||
<div className="text-lg font-normal max-w-full flex-initial">
|
||||
{title}
|
||||
<div className="text-sm font-base">{children}</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React, { createContext, FC, ReactNode, useMemo, useState } from 'react';
|
||||
import { omit } from 'ramda';
|
||||
|
||||
import { ToasterId } from '../Toaster/types';
|
||||
import { Modal, ModalId } from './types';
|
||||
|
||||
type ModalContextType = {
|
||||
|
@ -8,7 +9,7 @@ type ModalContextType = {
|
|||
open: (modalId: ModalId) => void;
|
||||
opened: ModalId[];
|
||||
register: (modal: Modal) => void;
|
||||
registered: Record<string, Modal>;
|
||||
registered: Record<ToasterId, Modal>;
|
||||
unregister: (modalId: ModalId) => void;
|
||||
};
|
||||
|
||||
|
@ -22,7 +23,7 @@ export const ModalContext = createContext<ModalContextType>({
|
|||
});
|
||||
|
||||
export const ModalWrapper: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [modals, setModals] = useState<Record<string, Modal>>({});
|
||||
const [modals, setModals] = useState<Record<ToasterId, Modal>>({});
|
||||
const [opened, setOpened] = useState<ModalId[]>([]);
|
||||
|
||||
const register = (modal: Modal) => {
|
||||
|
|
|
@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|||
|
||||
import clsx from 'clsx';
|
||||
|
||||
import Button from '@treejs/components/Button';
|
||||
import CircleIcon from '@treejs/components/Icons/Circle';
|
||||
import CircleFillIcon from '@treejs/components/Icons/CircleFill';
|
||||
import { STATUS } from '@treejs/types/common';
|
||||
|
@ -43,15 +42,21 @@ const RadioButton: React.FC<IRadioButtonFieldProps> = (props) => {
|
|||
[]
|
||||
);
|
||||
|
||||
const optionElements = useMemo(() => {
|
||||
const boxClassName = clsx('text-xl field-radio', {
|
||||
'field-radio--info': status === STATUS.info,
|
||||
'field-radio--warning': status === STATUS.warning,
|
||||
'field-radio--error': status === STATUS.error,
|
||||
});
|
||||
|
||||
const elements = useMemo(() => {
|
||||
return options.map(({ code, name }): React.ReactElement => {
|
||||
return (
|
||||
<div
|
||||
key={code}
|
||||
className={clsx({
|
||||
'mb-2 block': !horizontal,
|
||||
'inline-block mr-1': horizontal,
|
||||
'inline-block mr-5': horizontal,
|
||||
})}
|
||||
key={code}
|
||||
>
|
||||
{!isNilOrEmpty(OptionRender) ? (
|
||||
React.cloneElement(OptionRender, {
|
||||
|
@ -59,28 +64,24 @@ const RadioButton: React.FC<IRadioButtonFieldProps> = (props) => {
|
|||
value,
|
||||
} as any)
|
||||
) : (
|
||||
<Button
|
||||
label={
|
||||
<div className="flex justify-between items-center">
|
||||
{value === code ? <CircleFillIcon fontSize={20} /> : <CircleIcon fontSize={20} />}
|
||||
<b className="ml-2">{name}</b>
|
||||
<div className="flex items-center field-radio" onClick={handleClick({ code, name })}>
|
||||
{value === code ? (
|
||||
<CircleFillIcon className={boxClassName} />
|
||||
) : (
|
||||
<CircleIcon className={boxClassName} />
|
||||
)}
|
||||
<div className="ml-1">{name}</div>
|
||||
</div>
|
||||
}
|
||||
onClick={handleClick({ code, name })}
|
||||
className="m-0 normal-case w-auto font-thin"
|
||||
pressed={value === code}
|
||||
status={status}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}, [value, options, props.name, status]);
|
||||
}, [value, options, props.name, boxClassName]);
|
||||
|
||||
return (
|
||||
<div className="radiobutton-container">
|
||||
<div className="block text-black pb-2 field-label">{title}</div>
|
||||
{horizontal ? <div className="flex">{optionElements}</div> : optionElements}
|
||||
<div className="field-container radiobutton-container">
|
||||
<div className="field-label cursor-default mr-2">{title}</div>
|
||||
{elements}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { ReactChild, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { isNil } from 'ramda';
|
||||
|
||||
import clsx from 'clsx';
|
||||
|
@ -15,7 +15,7 @@ import { MenuItem } from '../types';
|
|||
import { Item } from './Item';
|
||||
|
||||
type IProps = {
|
||||
children: React.ReactChild;
|
||||
children: ReactChild;
|
||||
components?: {
|
||||
footer?: React.ReactElement;
|
||||
};
|
||||
|
|
|
@ -93,9 +93,9 @@ const TextField: React.FC<ITextFieldProps> = (props) => {
|
|||
|
||||
const handleClearValue = useCallback(
|
||||
(e: React.MouseEvent<HTMLInputElement>): void => {
|
||||
setValue(null);
|
||||
setValue('');
|
||||
if (onChange) {
|
||||
onChange(null, e);
|
||||
onChange('', e);
|
||||
}
|
||||
},
|
||||
[onChange]
|
||||
|
@ -114,19 +114,23 @@ const TextField: React.FC<ITextFieldProps> = (props) => {
|
|||
[disabled, onChange]
|
||||
);
|
||||
|
||||
const showClearIcon = !disabled && clearable && !isNilOrEmpty(value);
|
||||
|
||||
return (
|
||||
<div className="textfield-container">
|
||||
{title !== undefined && (
|
||||
<div className="field-container">
|
||||
{title && (
|
||||
<label className="field-label" htmlFor={name}>
|
||||
{title}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
className={clsx(className, 'textfield-input masked', {
|
||||
'textfield-input--info': status === STATUS.info,
|
||||
'textfield-input--success': status === STATUS.success,
|
||||
'textfield-input--warning': status === STATUS.warning,
|
||||
'textfield-input--error': status === STATUS.error,
|
||||
className={clsx(className, 'field-input masked', {
|
||||
'field-input--info': status === STATUS.info,
|
||||
'field-input--success': status === STATUS.success,
|
||||
'field-input--warning': status === STATUS.warning,
|
||||
'field-input--error': status === STATUS.error,
|
||||
'field-input--nolabel': !title,
|
||||
'field-input--clearable': showClearIcon,
|
||||
})}
|
||||
ref={inputRef}
|
||||
autoComplete="off"
|
||||
|
@ -142,7 +146,7 @@ const TextField: React.FC<ITextFieldProps> = (props) => {
|
|||
onChange={handleChange}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
{!disabled && clearable && !isNilOrEmpty(value) && (
|
||||
{showClearIcon && (
|
||||
<div className="field-icon field-icon--clear" onClick={handleClearValue}>
|
||||
<BackspaceIcon />
|
||||
</div>
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { Dispatch } from '@reduxjs/toolkit';
|
||||
|
||||
import { STATUS } from '@treejs/types/common';
|
||||
import { isNilOrEmpty } from '@treejs/utils';
|
||||
|
||||
import { TOASTER_TIMEOUT } from '../constants';
|
||||
import { ACTIONS, ToasterId, ToasterOption } from '../types';
|
||||
|
||||
const timeouts: { [key: string]: any } = [];
|
||||
|
||||
export const closeToaster = (name: ToasterId) => (dispatch: Dispatch) => {
|
||||
timeouts[name] = null;
|
||||
dispatch({
|
||||
type: ACTIONS.REMOVE_TOASTER,
|
||||
payload: {
|
||||
name,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const addToaster =
|
||||
(options: ToasterOption): any =>
|
||||
(dispatch: Dispatch<any>) => {
|
||||
if (!isNilOrEmpty(timeouts[options.name])) {
|
||||
clearTimeout(timeouts[options.name]);
|
||||
}
|
||||
|
||||
timeouts[options.name] = setTimeout(() => {
|
||||
dispatch(closeToaster(options.name));
|
||||
}, options.timeout || TOASTER_TIMEOUT);
|
||||
|
||||
dispatch({
|
||||
type: ACTIONS.ADD_TOASTER,
|
||||
payload: {
|
||||
status: STATUS.none,
|
||||
timeout: TOASTER_TIMEOUT,
|
||||
...options,
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,29 +1,23 @@
|
|||
import React, { useCallback } from 'react';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { STATUS } from '@treejs/types/common';
|
||||
import React from 'react';
|
||||
|
||||
import Button from '../../Button';
|
||||
import { addToaster } from '../actions';
|
||||
import { ToasterState } from '../types';
|
||||
import { useToaster } from '../hooks';
|
||||
import { Toaster } from '../types';
|
||||
|
||||
type IProps = {
|
||||
message: string;
|
||||
status?: STATUS;
|
||||
title: string;
|
||||
} & ToasterState;
|
||||
label: string;
|
||||
} & Toaster;
|
||||
|
||||
const AddToaster: React.FC<IProps> = (props) => {
|
||||
const { name, title, status, message } = props;
|
||||
const { id, label, title, status, message } = props;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { showToaster } = useToaster();
|
||||
|
||||
const handleClick = useCallback((): void => {
|
||||
dispatch(addToaster({ name, status, message }));
|
||||
}, []);
|
||||
const handleClick = (): void => {
|
||||
showToaster({ id, status, message, title });
|
||||
};
|
||||
|
||||
return <Button label={title} onClick={handleClick} status={status} />;
|
||||
return <Button label={label} onClick={handleClick} status={status} />;
|
||||
};
|
||||
|
||||
export default AddToaster;
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { isNilOrEmpty } from '@treejs/utils';
|
||||
|
||||
import Message from '../../Message';
|
||||
import { getToasters } from '../selectors';
|
||||
import { useToaster } from '../hooks';
|
||||
|
||||
function Toasters() {
|
||||
const toasters = useSelector(getToasters);
|
||||
const { toasters } = useToaster();
|
||||
|
||||
if (isNilOrEmpty(toasters)) {
|
||||
return null;
|
||||
|
@ -16,10 +14,10 @@ function Toasters() {
|
|||
|
||||
return (
|
||||
<div className="fixed bottom-4 right-6 sm-m:left-6 w-80 sm-m:w-auto z-10">
|
||||
{Object.keys(toasters).map((key) => {
|
||||
const toaster = toasters[key];
|
||||
{Object.keys(toasters).map((toasterId) => {
|
||||
const toaster = toasters[toasterId];
|
||||
return (
|
||||
<Message key={key} status={toaster.status} filled={toaster.filled}>
|
||||
<Message key={toasterId} status={toaster.status} title={toaster.title}>
|
||||
{toaster.message}
|
||||
</Message>
|
||||
);
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export const TOASTER_REDUCER_NAME = 'toasters';
|
||||
|
||||
export const TOASTER_TIMEOUT = 4000;
|
41
packages/components/src/Toaster/context.tsx
Normal file
41
packages/components/src/Toaster/context.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React, { createContext, FC, ReactNode, useMemo, useState } from 'react';
|
||||
import { omit } from 'ramda';
|
||||
|
||||
import { Toaster, ToasterId } from './types';
|
||||
|
||||
type ToasterContextType = {
|
||||
hide: (toasterId: ToasterId) => void;
|
||||
show: (toaster: Toaster) => void;
|
||||
toasters: Record<ToasterId, Toaster>;
|
||||
};
|
||||
|
||||
export const ToasterContext = createContext<ToasterContextType>({
|
||||
toasters: {},
|
||||
show: () => {},
|
||||
hide: () => {},
|
||||
});
|
||||
|
||||
export const ToasterWrapper: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [toasters, setToasters] = useState<Record<ToasterId, Toaster>>({});
|
||||
|
||||
const show = (toaster: Toaster) => {
|
||||
setToasters({ ...toasters, [toaster.id]: toaster });
|
||||
};
|
||||
|
||||
const hide = (toasterId: ToasterId) => {
|
||||
setToasters(omit([toasterId], toasters));
|
||||
};
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
toasters,
|
||||
show,
|
||||
hide,
|
||||
}),
|
||||
[toasters]
|
||||
);
|
||||
|
||||
return <ToasterContext.Provider value={value}>{children}</ToasterContext.Provider>;
|
||||
};
|
||||
|
||||
export default ToasterWrapper;
|
35
packages/components/src/Toaster/hooks.ts
Normal file
35
packages/components/src/Toaster/hooks.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { useContext, useEffect } from 'react';
|
||||
|
||||
import { ToasterContext } from './context';
|
||||
import { Toaster, ToasterId } from './types';
|
||||
|
||||
const TOASTER_TIMEOUT = 4000;
|
||||
|
||||
const timeouts: { [key: ToasterId]: any } = [];
|
||||
|
||||
export const useToaster = () => {
|
||||
const { show, hide, toasters } = useContext(ToasterContext);
|
||||
|
||||
const autoHide = (toaster: Toaster) => {
|
||||
const { id } = toaster;
|
||||
show(toaster);
|
||||
timeouts[id] = setTimeout(() => {
|
||||
hide(id);
|
||||
}, toaster.timeout || TOASTER_TIMEOUT);
|
||||
};
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
Object.keys(timeouts).forEach((timeoutId) => {
|
||||
clearTimeout(timeouts[timeoutId]);
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return {
|
||||
toasters,
|
||||
showToaster: autoHide,
|
||||
hideToaster: hide,
|
||||
};
|
||||
};
|
|
@ -1,31 +0,0 @@
|
|||
import { dissoc } from 'ramda';
|
||||
|
||||
import { Action } from '@treejs/types/redux/actions';
|
||||
import { isNilOrEmpty } from '@treejs/utils';
|
||||
|
||||
import { ACTIONS, ToasterReducer, ToasterState } from '../types';
|
||||
|
||||
const initialState: ToasterReducer = {};
|
||||
|
||||
type Payload = ToasterState;
|
||||
|
||||
const reducer = (state: typeof initialState, action: Action<Payload, ACTIONS>): ToasterReducer => {
|
||||
if (isNilOrEmpty(state)) {
|
||||
state = initialState;
|
||||
}
|
||||
|
||||
if (action.type === ACTIONS.ADD_TOASTER) {
|
||||
return {
|
||||
...state,
|
||||
[action.payload.name]: {
|
||||
...action.payload,
|
||||
},
|
||||
};
|
||||
} else if (action.type === ACTIONS.REMOVE_TOASTER) {
|
||||
return dissoc(action.payload.name, state);
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
|
@ -1,6 +0,0 @@
|
|||
import { ROOT_REDUCER_NAME } from '@treejs/constants/redux';
|
||||
|
||||
import { TOASTER_REDUCER_NAME } from '../constants';
|
||||
import { ToasterReducer } from '../types';
|
||||
|
||||
export const getToasters = (state: any): ToasterReducer => state[ROOT_REDUCER_NAME][TOASTER_REDUCER_NAME];
|
11
packages/components/src/Toaster/types.ts
Normal file
11
packages/components/src/Toaster/types.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { STATUS } from '@treejs/types/common';
|
||||
|
||||
export type ToasterId = string;
|
||||
|
||||
export type Toaster = {
|
||||
id: ToasterId;
|
||||
message: string;
|
||||
status?: STATUS;
|
||||
timeout?: number;
|
||||
title?: string;
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
import { STATUS } from '@treejs/types/common';
|
||||
|
||||
export enum ACTIONS {
|
||||
ADD_TOASTER = 'TOASTER/ADD_TOASTER',
|
||||
REMOVE_TOASTER = 'TOASTER/REMOVE_TOASTER',
|
||||
}
|
||||
|
||||
export type ToasterId = string;
|
||||
|
||||
export type ToasterState = {
|
||||
filled?: boolean;
|
||||
message: string;
|
||||
name: ToasterId;
|
||||
status?: STATUS;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
export type ToasterOption = ToasterState;
|
||||
|
||||
export interface ToasterReducer {
|
||||
[key: string]: ToasterState;
|
||||
}
|
|
@ -37,7 +37,7 @@ function Field<F>(props: IProps<F>) {
|
|||
break;
|
||||
}
|
||||
|
||||
return <div className="field-container">{render}</div>;
|
||||
return <div className="form-field-container">{render}</div>;
|
||||
}
|
||||
|
||||
export default Field;
|
||||
|
|
|
@ -26,7 +26,9 @@ const button = (theme) => ({
|
|||
width: '100%',
|
||||
height: '40px',
|
||||
|
||||
borderRadius: theme('borderRadius.sm'),
|
||||
fontSize: '14px',
|
||||
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
border: '2px solid',
|
||||
|
||||
textTransform: 'uppercase',
|
||||
|
|
|
@ -1,120 +1,97 @@
|
|||
const buildState = ({ borderColor }) => {
|
||||
return {
|
||||
borderColor: borderColor,
|
||||
boxShadow: `0 4px ${borderColor}`,
|
||||
'&:hover:not(:disabled)': {
|
||||
boxShadow: `0 2px ${borderColor}`,
|
||||
transform: 'translateY(2px)',
|
||||
},
|
||||
'&:focus:not(:disabled)': {
|
||||
boxShadow: `0 0 ${borderColor}`,
|
||||
transform: 'translateY(4px)',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const field = (theme) => ({
|
||||
'.field-container': {
|
||||
marginBottom: '1rem',
|
||||
'.form-field-container': {
|
||||
marginBottom: '.5rem',
|
||||
},
|
||||
'.textfield-container': {
|
||||
'.field': {
|
||||
'&-container': {
|
||||
position: 'relative',
|
||||
'& svg': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: '15px',
|
||||
},
|
||||
'&-label': {
|
||||
padding: '0.25rem 1rem 0.25rem 0.5rem',
|
||||
color: theme('colors.field.label.color'),
|
||||
backgroundColor: theme('colors.field.label.bg'),
|
||||
borderRadius: `${theme('borderRadius.md')} 0 0 ${theme('borderRadius.md')}`,
|
||||
border: `2px solid ${theme('colors.field.border')}`,
|
||||
whiteSpace: 'nowrap',
|
||||
cursor: 'pointer',
|
||||
// minWidth: '9rem',
|
||||
},
|
||||
'.textfield-input': {
|
||||
padding: '0.5rem',
|
||||
|
||||
"&:not([value=''])": {
|
||||
transform: 'translateY(4px)',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
|
||||
'&-input': {
|
||||
padding: '0.25rem',
|
||||
outline: 'none',
|
||||
width: '100%',
|
||||
|
||||
lineHeight: '1.25',
|
||||
|
||||
fontSize: '1rem',
|
||||
fontWeight: 300,
|
||||
|
||||
boxSizing: 'border-box',
|
||||
borderRadius: theme('borderRadius.sm'),
|
||||
borderRadius: `0 ${theme('borderRadius.md')} ${theme('borderRadius.md')} 0`,
|
||||
border: '2px solid',
|
||||
|
||||
'&': buildState({
|
||||
borderColor: theme('colors.gray.300'),
|
||||
}),
|
||||
|
||||
'&--error': buildState({
|
||||
borderLeft: 'none',
|
||||
'&': {
|
||||
borderColor: theme('colors.field.border'),
|
||||
},
|
||||
'&--error': {
|
||||
borderColor: theme('colors.error.DEFAULT'),
|
||||
}),
|
||||
'&--info': buildState({
|
||||
},
|
||||
'&--info': {
|
||||
borderColor: theme('colors.info.DEFAULT'),
|
||||
}),
|
||||
'&--warning': buildState({
|
||||
},
|
||||
'&--warning': {
|
||||
borderColor: theme('colors.warning.DEFAULT'),
|
||||
}),
|
||||
'&--success': buildState({
|
||||
borderColor: theme('colors.success.DEFAULT'),
|
||||
}),
|
||||
|
||||
},
|
||||
'&:disabled': {
|
||||
backgroundColor: 'transparent',
|
||||
border: `2px solid ${theme('colors.whitesmoke')}`,
|
||||
'&:empty': {
|
||||
border: 'none',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
'&:read-only': {
|
||||
cursor: 'auto',
|
||||
},
|
||||
'&--nolabel': {
|
||||
borderLeft: '2px solid',
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
},
|
||||
'&--clearable': {
|
||||
borderRadius: 0,
|
||||
borderRight: 'none',
|
||||
},
|
||||
},
|
||||
'.checkbox-container': {
|
||||
position: 'relative',
|
||||
marginTop: '1rem',
|
||||
},
|
||||
'.radiobutton-container': {
|
||||
position: 'relative',
|
||||
marginTop: '1rem',
|
||||
'.checkbox-container, .radiobutton-container': {
|
||||
'.field-label': {
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
},
|
||||
'.field-checkbox, .field-radio': {
|
||||
cursor: 'pointer',
|
||||
'&--error': {
|
||||
color: theme('colors.error.DEFAULT'),
|
||||
},
|
||||
'&--info': {
|
||||
color: theme('colors.info.DEFAULT'),
|
||||
},
|
||||
'&--warning': {
|
||||
color: theme('colors.warning.DEFAULT'),
|
||||
},
|
||||
},
|
||||
},
|
||||
'.field-icon': {
|
||||
position: 'absolute',
|
||||
top: '1.27rem',
|
||||
right: '0',
|
||||
padding: '0.51rem',
|
||||
|
||||
color: theme('colors.primary.DEFAULT'),
|
||||
|
||||
fontSize: '1.125rem',
|
||||
lineHeight: '1.25rem',
|
||||
|
||||
'&--clear': {
|
||||
display: 'none',
|
||||
borderRadius: '0 2px 2px 0',
|
||||
color: theme('colors.error.dark'),
|
||||
},
|
||||
padding: '0.4rem 0.5rem',
|
||||
color: theme('colors.white'),
|
||||
backgroundColor: theme('colors.field.label.bg'),
|
||||
borderRadius: `0 ${theme('borderRadius.md')} ${theme('borderRadius.md')} 0`,
|
||||
border: `2px solid ${theme('colors.field.border')}`,
|
||||
cursor: 'pointer',
|
||||
'.field-input:disabled &': {
|
||||
color: theme('colors.gray.500'),
|
||||
'& svg': {
|
||||
cursor: 'auto',
|
||||
},
|
||||
},
|
||||
'.field-input:hover + &, .field-input + &:hover': {
|
||||
display: 'inline',
|
||||
},
|
||||
},
|
||||
'.field-label': {
|
||||
fontSize: '0.8rem',
|
||||
lineHeight: '1.25rem',
|
||||
color: theme('colors.gray.500'),
|
||||
},
|
||||
'.field-validationMessage': {
|
||||
color: theme('colors.error.DEFAULT'),
|
||||
textAlign: 'left',
|
||||
fontSize: '0.7rem',
|
||||
fontSize: '0.75rem',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
const buildState = ({ color, borderColor, backgroundColor, iconColor }) => {
|
||||
return {
|
||||
color,
|
||||
borderColor,
|
||||
backgroundColor,
|
||||
'& svg': {
|
||||
color: iconColor ? iconColor : 'white',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const message = (theme) => ({
|
||||
'.message': {
|
||||
padding: theme('spacing.4'),
|
||||
|
@ -12,40 +23,34 @@ const message = (theme) => ({
|
|||
backgroundColor: 'white',
|
||||
|
||||
border: '3px solid whitesmoke',
|
||||
borderRadius: theme('borderRadius.sm'),
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
|
||||
'&--none': {
|
||||
borderColor: 'transparent',
|
||||
'&..message--filled': {
|
||||
backgroundColor: theme('transparent'),
|
||||
},
|
||||
},
|
||||
'&--info': {
|
||||
borderColor: theme('colors.info'),
|
||||
'&.message--filled': {
|
||||
backgroundColor: theme('colors.info'),
|
||||
},
|
||||
},
|
||||
'&--success': {
|
||||
borderColor: theme('colors.success'),
|
||||
'&.message--filled': {
|
||||
backgroundColor: theme('colors.success'),
|
||||
},
|
||||
},
|
||||
'&--warning': {
|
||||
'&--none': buildState({
|
||||
color: theme('colors.black'),
|
||||
borderColor: theme('colors.warning'),
|
||||
'&.message--filled': {
|
||||
backgroundColor: theme('colors.warning'),
|
||||
},
|
||||
},
|
||||
'&--error': {
|
||||
borderColor: theme('colors.gray.300'),
|
||||
backgroundColor: theme('colors.whitesmoke'),
|
||||
iconColor: theme('colors.gray.700'),
|
||||
}),
|
||||
'&--info': buildState({
|
||||
color: theme('colors.black'),
|
||||
borderColor: theme('colors.error.DEFAULT'),
|
||||
'&.message--filled': {
|
||||
borderColor: theme('colors.info.dark'),
|
||||
backgroundColor: theme('colors.info.DEFAULT'),
|
||||
}),
|
||||
'&--success': buildState({
|
||||
color: theme('colors.black'),
|
||||
borderColor: theme('colors.success.dark'),
|
||||
backgroundColor: theme('colors.success.DEFAULT'),
|
||||
}),
|
||||
'&--warning': buildState({
|
||||
color: theme('colors.black'),
|
||||
borderColor: theme('colors.warning.dark'),
|
||||
backgroundColor: theme('colors.warning.DEFAULT'),
|
||||
}),
|
||||
'&--error': buildState({
|
||||
color: theme('colors.black'),
|
||||
borderColor: theme('colors.error.dark'),
|
||||
backgroundColor: theme('colors.error.DEFAULT'),
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ const skeleton = (theme) => ({
|
|||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: '14px',
|
||||
fontSize: '16px',
|
||||
cursor: 'pointer',
|
||||
'&-left': {
|
||||
display: 'flex',
|
||||
|
@ -36,8 +36,8 @@ const skeleton = (theme) => ({
|
|||
'.side': {
|
||||
color: theme('colors.navigation.side.color'),
|
||||
backgroundColor: theme('colors.navigation.side.bg'),
|
||||
borderRadius: theme('borderRadius.sm'),
|
||||
boxShadow: '1px -1px 8px -1px rgba(var(--color-black), 0.4)',
|
||||
border: `2px solid ${theme('colors.navigation.side.border')}`,
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
position: 'relative',
|
||||
paddingTop: '2.5rem',
|
||||
display: 'flex',
|
||||
|
@ -62,14 +62,14 @@ const skeleton = (theme) => ({
|
|||
width: '2.25rem',
|
||||
'.menu-item': {
|
||||
'&-left': {
|
||||
padding: '0.5rem 0 0.5rem 0.25rem',
|
||||
padding: '0.5rem 0 0.5rem 0.125rem',
|
||||
},
|
||||
'&-right': {
|
||||
padding: '0.5rem 0.25rem 0.5rem 0',
|
||||
fontSize: '8px',
|
||||
},
|
||||
'&-openIcon': {
|
||||
fontSize: '8px',
|
||||
fontSize: '0.5rem',
|
||||
},
|
||||
'&-tooltip': {
|
||||
color: theme('colors.navigation.side.color'),
|
||||
|
@ -90,8 +90,7 @@ const skeleton = (theme) => ({
|
|||
left: '2.5rem',
|
||||
padding: '0.25rem 1rem',
|
||||
backgroundColor: theme('colors.black.DEFAULT'),
|
||||
boxShadow: '1px 1px 8px -1px rgba(var(--color-black), 0.4)',
|
||||
borderRadius: theme('borderRadius.sm'),
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
display: 'none',
|
||||
},
|
||||
'&:hover .menu-item-tooltip': {
|
||||
|
@ -100,7 +99,9 @@ const skeleton = (theme) => ({
|
|||
},
|
||||
'.submenu': {
|
||||
'&-container': {
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
backgroundColor: theme('colors.subnavigation.side.bg'),
|
||||
border: `2px solid ${theme('colors.navigation.side.bg')}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -113,9 +114,10 @@ const skeleton = (theme) => ({
|
|||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: '0.25rem',
|
||||
height: '2.5rem',
|
||||
backgroundColor: theme('colors.navigation.top.bg'),
|
||||
borderRadius: theme('borderRadius.sm'),
|
||||
boxShadow: '1px 1px 8px -1px rgba(var(--color-black), 0.4)',
|
||||
border: `2px solid ${theme('colors.navigation.top.border')}`,
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
'&-toggler': {
|
||||
cursor: 'pointer',
|
||||
fontSize: '1.75rem',
|
||||
|
@ -142,12 +144,13 @@ const skeleton = (theme) => ({
|
|||
},
|
||||
'.submenu': {
|
||||
'&-container': {
|
||||
marginTop: '.125rem',
|
||||
zIndex: 1,
|
||||
marginTop: '.5rem',
|
||||
width: 'max-content',
|
||||
boxShadow: '1px 5px 8px -1px rgba(var(--color-black), 0.4)',
|
||||
borderRadius: '2px',
|
||||
position: 'absolute',
|
||||
backgroundColor: theme('colors.subnavigation.top.bg'),
|
||||
border: `2px solid ${theme('colors.navigation.top.bg')}`,
|
||||
},
|
||||
'&-item': {
|
||||
margin: '0',
|
||||
|
@ -162,6 +165,7 @@ const skeleton = (theme) => ({
|
|||
[`@media (max-width: ${theme('screens.md-m.max')})`]: {
|
||||
'.page-main': {
|
||||
'.top': {
|
||||
height: 'auto',
|
||||
flexDirection: 'column',
|
||||
'.menu-container': {
|
||||
width: '100%',
|
||||
|
@ -170,7 +174,7 @@ const skeleton = (theme) => ({
|
|||
'&-container': {
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
boxShadow: 'none',
|
||||
borderRadius: theme('borderRadius.md'),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -29,18 +29,24 @@
|
|||
--color-success--dark: theme('colors.green.400');
|
||||
--color-warning: theme('colors.yellow.300');
|
||||
--color-warning--dark: theme('colors.yellow.400');
|
||||
--color-error: theme('colors.red.300');
|
||||
--color-error--dark: theme('colors.red.400');
|
||||
--color-error: theme('colors.red.400');
|
||||
--color-error--dark: theme('colors.red.500');
|
||||
|
||||
--color-navigation-top-color: white;
|
||||
--color-navigation-top-bg: #3A6891;
|
||||
--color-navigation-top-border: #213C53;
|
||||
--color-subnavigation-top-color: white;
|
||||
--color-subnavigation-top-bg: #477FB1;
|
||||
|
||||
--color-navigation-side-color: white;
|
||||
--color-navigation-side-bg: #3B4566;
|
||||
--color-navigation-side-border: #22283B;
|
||||
--color-subnavigation-side-color: white;
|
||||
--color-subnavigation-side-bg: #4E5B87;
|
||||
|
||||
--color-field-label-color: black;
|
||||
--color-field-label-bg: theme('colors.gray.300');
|
||||
--color-field-border: theme('colors.gray.400');
|
||||
}
|
||||
|
||||
.purple {
|
||||
|
|
|
@ -45,10 +45,12 @@ module.exports = {
|
|||
navigation: {
|
||||
top: {
|
||||
color: 'var(--color-navigation-top-color)',
|
||||
border: 'var(--color-navigation-top-border)',
|
||||
bg: 'var(--color-navigation-top-bg)',
|
||||
},
|
||||
side: {
|
||||
color: 'var(--color-navigation-side-color)',
|
||||
border: 'var(--color-navigation-side-border)',
|
||||
bg: 'var(--color-navigation-side-bg)',
|
||||
},
|
||||
},
|
||||
|
@ -62,6 +64,13 @@ module.exports = {
|
|||
bg: 'var(--color-subnavigation-side-bg)',
|
||||
},
|
||||
},
|
||||
field: {
|
||||
label: {
|
||||
color: 'var(--color-field-label-color)',
|
||||
bg: 'var(--color-field-label-bg)',
|
||||
},
|
||||
border: 'var(--color-field-border)',
|
||||
},
|
||||
},
|
||||
screens: {
|
||||
'lg-m': { min: '768px', max: '1024px' },
|
||||
|
|
|
@ -2,9 +2,9 @@ import { ArgsTable, Meta, Preview, Story } from '@storybook/addon-docs';
|
|||
|
||||
import AddToaster from '@treejs/components/Toaster/components/AddToaster';
|
||||
import Toasters from '@treejs/components/Toaster/components/Toasters';
|
||||
import ToasterWrapper from '@treejs/components/Toaster/context';
|
||||
|
||||
import { STATUS } from '../../packages/types/src/common';
|
||||
import StoreProvider from './Toaster/redux';
|
||||
|
||||
<Meta title={'Components/Toaster/AddToaster'} component={AddToaster} />
|
||||
|
||||
|
@ -16,31 +16,14 @@ import StoreProvider from './Toaster/redux';
|
|||
|
||||
### Requirements
|
||||
|
||||
1. Wrapped with redux store provider.
|
||||
|
||||
```tsx dark
|
||||
import { TOASTER_REDUCER_NAME } from '@treejs/components/Toaster/constants';
|
||||
import toasterReducer from '@treejs/components/Toaster/reducer';
|
||||
import { ROOT_REDUCER_NAME } from '@treejs/constants/redux';
|
||||
|
||||
export const mainReducer = combineReducers({
|
||||
[ROOT_REDUCER_NAME]: combineReducers({
|
||||
[TOASTER_REDUCER_NAME]: toasterReducer,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
2. Somewhere in redux store provider must be used `Toasters` component
|
||||
from `@treejs/components/Toaster/components/Toasters`
|
||||
|
||||
<ArgsTable />
|
||||
1. Use `ToasterWrapper` component for apply Toaster context
|
||||
|
||||
export const Template = (args) => {
|
||||
return (
|
||||
<StoreProvider>
|
||||
<ToasterWrapper>
|
||||
<AddToaster {...args} />
|
||||
<Toasters />
|
||||
</StoreProvider>
|
||||
</ToasterWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -53,6 +36,7 @@ export const Template = (args) => {
|
|||
args={{
|
||||
name: 'none',
|
||||
title: 'Show toaster',
|
||||
label: 'Show toaster',
|
||||
message: 'Toaster message.',
|
||||
status: STATUS.none,
|
||||
}}
|
||||
|
@ -70,6 +54,7 @@ export const Template = (args) => {
|
|||
args={{
|
||||
name: 'info',
|
||||
title: 'Show toaster',
|
||||
label: 'Show toaster',
|
||||
message: 'Toaster message.',
|
||||
status: STATUS.info,
|
||||
}}
|
||||
|
@ -87,6 +72,7 @@ export const Template = (args) => {
|
|||
args={{
|
||||
name: 'success',
|
||||
title: 'Show toaster',
|
||||
label: 'Show toaster',
|
||||
message: 'Toaster message.',
|
||||
status: STATUS.success,
|
||||
}}
|
||||
|
@ -104,6 +90,7 @@ export const Template = (args) => {
|
|||
args={{
|
||||
name: 'warning',
|
||||
title: 'Show toaster',
|
||||
label: 'Show toaster',
|
||||
message: 'Toaster message.',
|
||||
status: STATUS.warning,
|
||||
}}
|
||||
|
@ -121,6 +108,7 @@ export const Template = (args) => {
|
|||
args={{
|
||||
name: 'error',
|
||||
title: 'Show toaster',
|
||||
label: 'Show toaster',
|
||||
message: 'Toaster message.',
|
||||
status: STATUS.error,
|
||||
}}
|
||||
|
|
|
@ -5,8 +5,10 @@ import CustomRouter from '@treejs/components/CustomRouter';
|
|||
import MinusIcon from '@treejs/components/Icons/Minus';
|
||||
import PlusIcon from '@treejs/components/Icons/Plus';
|
||||
import UserIcon from '@treejs/components/Icons/User';
|
||||
import { ModalWrapper } from '@treejs/components/Modal/context';
|
||||
import Skeleton from '@treejs/components/Skeleton';
|
||||
|
||||
import FormExample from '../form/components/ExampleForm';
|
||||
import Router from './Skeleton/Router';
|
||||
|
||||
<Meta title={'Components/Sketeton'} component={Skeleton} />
|
||||
|
@ -21,9 +23,12 @@ export const history = createBrowserHistory({ basename: '/' });
|
|||
|
||||
export const Template = (args) => (
|
||||
<CustomRouter history={history}>
|
||||
<ModalWrapper>
|
||||
<Skeleton {...args} history={history}>
|
||||
<Router />
|
||||
<FormExample />
|
||||
</Skeleton>
|
||||
</ModalWrapper>
|
||||
</CustomRouter>
|
||||
);
|
||||
|
||||
|
|
|
@ -13,27 +13,10 @@ import { Meta } from '@storybook/addon-docs';
|
|||
|
||||
### Requirements
|
||||
|
||||
1. Wrapped with redux store provider.
|
||||
1. Use `ToasterWrapper` component for apply Toaster context
|
||||
|
||||
## Open Toaster
|
||||
|
||||
```ts dark
|
||||
import { combineReducers } from '@reduxjs/toolkit';
|
||||
|
||||
import { TOASTER_REDUCER_NAME } from '@treejs/components/Toaster/constants';
|
||||
import toasterReducer from '@treejs/components/Toaster/reducer';
|
||||
import { ROOT_REDUCER_NAME } from '@treejs/constants/redux';
|
||||
|
||||
export const mainReducer = combineReducers({
|
||||
[ROOT_REDUCER_NAME]: combineReducers({
|
||||
[TOASTER_REDUCER_NAME]: toasterReducer,
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
2. Somewhere in redux store provider must be used `Toasters` component
|
||||
from `@treejs/components/Toaster/components/Toasters`
|
||||
|
||||
## Redux action
|
||||
|
||||
```ts dark
|
||||
dispatch(addToaster({ name, status, message }));
|
||||
showToaster({ id, status, message });
|
||||
```
|
||||
|
|
Loading…
Add table
Reference in a new issue