Remove Redux from Toaster component (treejs#6) + field visual update

Change-Id: I3aaab206f6d78a628d2e4b1e9ea0422fa39af589
This commit is contained in:
Roman Jaroš 2023-04-10 12:33:56 +02:00
parent 5f2ad09b39
commit 23daaab869
26 changed files with 321 additions and 359 deletions

View file

@ -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>
);
};

View file

@ -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>

View file

@ -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) => {

View file

@ -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>
);
};

View file

@ -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;
};

View file

@ -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>

View file

@ -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,
},
});
};

View file

@ -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;

View file

@ -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>
);

View file

@ -1,3 +0,0 @@
export const TOASTER_REDUCER_NAME = 'toasters';
export const TOASTER_TIMEOUT = 4000;

View 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;

View 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,
};
};

View file

@ -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;

View file

@ -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];

View 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;
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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',

View file

@ -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',
},
});

View file

@ -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'),
},
},
}),
},
});

View file

@ -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'),
},
},
},

View file

@ -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 {

View file

@ -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' },

View file

@ -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,
}}

View file

@ -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>
);

View file

@ -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 });
```