react-use#useClickAway TypeScript Examples
The following examples show how to use
react-use#useClickAway.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: useGlobalClickAway.ts From UUI with MIT License | 5 votes |
export function useGlobalClickAway(active: boolean, elementRef: any, onClickAway?: (event: Event) => void) {
const activateElement = useCallback((element: any) => {
let foundIndex = -1
do {
foundIndex = GlobalActiveElements.findIndex((i) => i === element)
if (foundIndex !== -1) GlobalActiveElements.splice(foundIndex, 1)
} while (foundIndex !== -1);
GlobalActiveElements.push(element)
}, [])
const deactivateElement = useCallback((element: any) => {
const foundIndex = GlobalActiveElements.findIndex((i) => i === element)
if (foundIndex === -1) return
GlobalActiveElements.splice(foundIndex, Number.MAX_SAFE_INTEGER);
}, [])
const isCurrentActiveElement = useCallback((element: any) => {
return (GlobalActiveElements.length > 0 && GlobalActiveElements[GlobalActiveElements.length-1] === element)
}, [])
useEffect(() => {
if (!elementRef.current) return;
const targetElement = elementRef.current
if (active) {
activateElement(targetElement)
} else {
deactivateElement(targetElement)
}
return () => {
deactivateElement(targetElement)
}
}, [activateElement, deactivateElement, active, elementRef])
useClickAway(elementRef, (event) => {
if (active) {
if (elementRef.current && !isCurrentActiveElement(elementRef.current)) return;
setTimeout(() => {
onClickAway && onClickAway(event)
}, 0)
}
}, ['mouseup', 'touchend'])
}
Example #2
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 5 votes |
Announcement = () => {
const [index, setIndex] = React.useState(1);
const [visible, setVisible] = React.useState(false);
const ref = React.useRef(null);
useClickAway(ref, () => {
setVisible(false);
});
const announcementList = layoutStore.useStore((s) => s.announcementList);
if (!announcementList.length) {
return null;
}
const total = announcementList.length;
const content = (
<div ref={ref} style={{ width: 400 }} className="px-4 pb-3 rounded-sm shadow-card-lg bg-default text-white">
<div className="h-12 flex items-center">
<ErdaIcon type="tonggao" fill="blue" size={20} />
<span className="ml-1">{i18n.t('layout:announcement')}</span>
</div>
<div className="text-white overflow-auto break-word pr-2" style={{ height: 130 }}>
{announcementList[index - 1].content}
</div>
<div className="h-8 flex items-center justify-end select-none">
<ErdaIcon
className={`rounded-sm w-8 h-8 text-white-6 hover:text-white hover:bg-white-2 cursor-pointer`}
type="left"
size={20}
onClick={() => setIndex(Math.max(index - 1, 1))}
/>
<div className="w-12 inline-flex items-center justify-center text-white-6">
{index} / {announcementList.length}
</div>
<ErdaIcon
className={`rounded-sm w-8 h-8 text-white-6 hover:text-white hover:bg-white-2 cursor-pointer`}
type="right"
size={20}
onClick={() => setIndex(Math.min(index + 1, total))}
/>
</div>
</div>
);
return (
<Dropdown overlay={content} placement="bottomRight" visible={visible}>
<div
className="flex items-center px-2 h-7 cursor-pointer rounded-full bg-blue text-white"
onClick={!visible ? () => setVisible(true) : undefined}
>
<ErdaIcon type="tonggao" fill="white" size={20} />
{i18n.t('layout:announcement')}
</div>
</Dropdown>
);
}
Example #3
Source File: Dropdown.tsx From oxen-website with GNU General Public License v3.0 | 5 votes |
export function Dropdown(props: Props) {
// Ensure children are all DropdownItems
const {
isOpen,
pull = 'right',
style = 'default',
center = false,
offsetX,
offsetY,
onClickAway,
children,
} = props;
const ref = useRef(null);
useClickAway(ref, onClickAway);
return (
<div className="relative z-50 w-full h-0">
<div
style={{
width: 'max-content',
marginLeft: offsetX ? `${offsetX}px` : 'unset',
marginTop: offsetY ? `${offsetY}px` : '0.5rem',
}}
className={classNames(
'absolute',
'top-0',
'z-50',
isOpen ? 'block' : 'hidden',
pull === 'right' && 'left-0',
pull === 'left' && 'right-0',
pull === 'center' && 'left-0 right-0',
)}
>
<div
ref={ref}
className={classNames(
'bg-white',
'duration-300',
'rounded-lg',
'transform',
'shadow-lg',
'overflow-hidden',
'last:border-b-0',
style === 'default' && ['pt-2'],
style === 'outline' && ['py-2', 'border-2', 'border-secondary'],
)}
>
{children}
</div>
</div>
</div>
);
}
Example #4
Source File: SideMenuSplit.tsx From oxen-website with GNU General Public License v3.0 | 5 votes |
export function SideMenuSplit() {
const { pageType, sideMenuExpanded } = useSelector(
(state: IState) => state.navigation,
);
const ref = useRef(null);
const dispatch = useDispatch();
const onClickAway = () => {
if (sideMenuExpanded && pageType === PageType.NORMAL) {
dispatch(collapseSideMenu());
}
};
useClickAway(ref, onClickAway);
const transform =
pageType === PageType.NORMAL || sideMenuExpanded
? 'translateX(0)'
: `translateX(-100%) translateX(${UI.SIDE_MENU_SIDE_BAR_WIDTH_PX - 3}px)`;
return (
<div
ref={ref}
style={{
// minWidth: pageType === PageType.NORMAL ? '50vw' : '0',
zIndex: 20033,
height:
pageType === PageType.NORMAL
? 'unset'
: `calc(100vh - ${UI.HEADER_HEIGHT_PX}px`,
transform,
}}
className={classNames(
'relative flex text-primary bg-alt duration-300 z-50',
)}
>
{pageType === PageType.NORMAL && (
<div
style={{
height: `calc(100vh - ${UI.HEADER_HEIGHT_PX}px`,
}}
className="w-full overflow-y-auto"
>
<SideMenuInner />
</div>
)}
<SideMenuSideBar mode={SideBarMode.LABEL} />
</div>
);
}
Example #5
Source File: page.tsx From platyplus with MIT License | 5 votes |
Page: React.FC = () => {
const { slug } = useParams()
const { state: contents, setState: setContents } = usePage<Descendant[]>({
slug,
path: 'contents'
})
const isConfigEnabled = useConfigEnabled()
const { state: title, setState: setTitle } = usePageTitle({ slug })
const ref = useRef(null)
useClickAway(ref, () => setEditing(false))
const edit = () => isConfigEnabled && setEditing(true)
const [editing, setEditing] = useState(false)
return (
<HeaderTitleWrapper
title={title}
component={
<InlineValue
editable={isConfigEnabled}
value={title}
onChange={setTitle}
/>
}
>
<Animation.Fade in={!!contents}>
{(props) => (
<div {...props} ref={ref} style={{ height: '100%' }} onClick={edit}>
{contents && (
<RichText
readOnly={!editing}
value={contents}
onChange={setContents}
/>
)}
</div>
)}
</Animation.Fade>
</HeaderTitleWrapper>
)
}
Example #6
Source File: ModalBase.tsx From rcvr-app with GNU Affero General Public License v3.0 | 5 votes |
ModalBase: React.FC<ModalBaseProps & Props> = ({
open,
onClose,
children,
maxWidth,
loading,
title,
...rest
}) => {
const ref = React.useRef()
const close = React.useCallback(() => {
if (loading) return
onClose()
}, [loading, onClose])
useClickAway(ref, close)
React.useEffect(() => {
if (open) document.body.classList.add('no-scroll')
if (!open) document.body.classList.remove('no-scroll')
}, [open])
return (
<AnimatePresence>
{open && (
<Overlay
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
css={{ pointerEvents: open ? 'initial' : 'none' }}
>
<ModalBox
ref={ref}
{...rest}
css={{ maxWidth }}
initial={{ scale: 1.1 }}
animate={{ scale: 1 }}
exit={{ scale: 0.9 }}
>
<Loading show={loading} />
<CloseButton onClose={close} />
<Box height={4} />
{title && (
<Box px={10}>
<Text variant="h3" textAlign="center">
{title}
</Text>
<Box height={6} />
</Box>
)}
{children}
</ModalBox>
</Overlay>
)}
</AnimatePresence>
)
}
Example #7
Source File: ListHeader.tsx From nextjs-hasura-fullstack with MIT License | 5 votes |
ListHeader: React.FC<ListHeaderProps> = (props) => {
const {
list: { id, name, position },
} = props
const [updateList] = useUpdateListMutation()
const onSubmit = async (newName: string) => {
try {
await updateList({
variables: { id, name: newName, position },
})
} catch (err) {
console.log(`?? [Error]: onSubmit`, Object.values(err))
}
}
const [isEditting, setIsEditting] = React.useState(!name)
const [value, setValue] = React.useState(name.toUpperCase())
const ref = React.useRef(null)
useClickAway(ref, () => {
if (isEditting) {
if (value && value !== name) {
onSubmit(value)
} else {
setValue(name)
}
setIsEditting(false)
}
})
return (
<div className={`flex w-32 h-8`}>
{!isEditting && (
<Button
isBlock
variant="light"
size="sm"
className={``}
onClick={() => {
setIsEditting(true)
}}
>
{value}
</Button>
)}
{isEditting && (
<Input
ref={ref}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true}
value={value}
onChange={(e) => {
setValue(e.target.value.toUpperCase())
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onSubmit(value)
setIsEditting(false)
}
}}
/>
)}
{/* {!!cards.length && <Badge variant="light">{cards.length}</Badge>} */}
</div>
)
}
Example #8
Source File: resource.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
ApiResource = (props: Merge<CP_API_RESOURCE.Props, API_SETTING.IResourceProps>) => {
const { quotePathMap, onQuoteChange, apiName, apiDetail } = props;
const [
{ currentMethod, apiMethodDetail, curTabKey, apiDisplayName, tempApiData, pathParams, open },
updater,
update,
] = useUpdate({
currentMethod: API_METHODS.get as API_SETTING.ApiMethod,
apiMethodDetail: {} as Obj,
curTabKey: API_RESOURCE_TAB.Summary,
apiDisplayName: '',
tempApiData: {},
pathParams: null,
open: false,
});
const { apiData, execOperation, operations } = props?.data || {};
const formRef = React.useRef<IFormExtendType>({} as any);
const [openApiDoc, apiLockState, formErrorNum] = apiDesignStore.useStore((s) => [
s.openApiDoc,
s.apiLockState,
s.formErrorNum,
]);
const { updateOpenApiDoc, updateFormErrorNum } = apiDesignStore;
const dataPath = React.useMemo(() => [apiName, currentMethod], [apiName, currentMethod]);
React.useEffect(() => {
if (apiData?.apiMethod) {
// 适配组件化协议的内容
updater.apiMethodDetail(apiData);
updater.tempApiData(!isEmpty(tempApiData) ? tempApiData : apiData);
} else {
// 点击左侧api列表导致的内容变化,更新第一个不为空的method,更新resource内容,resource内容切换到summary
let initialMethod = API_METHODS.get;
some(API_METHODS, (method) => {
if (!isEmpty(apiDetail[method])) {
initialMethod = method;
return true;
} else {
return false;
}
});
updater.currentMethod(initialMethod);
const _apiMethodDetail = apiDetail[initialMethod] || {};
updater.apiMethodDetail(_apiMethodDetail);
}
updater.pathParams(null);
updater.curTabKey(API_RESOURCE_TAB.Summary); // API切换后重置tab
}, [apiDetail, apiData, updater, tempApiData]);
React.useEffect(() => {
let _name = '';
if (apiData) {
_name = tempApiData.apiName || apiData?.apiName;
} else if (apiName) {
_name = apiName;
}
if (_name) {
updater.apiDisplayName(_name);
setTimeout(() => {
formRef.current.setFieldsValue({ apiName: _name });
});
}
}, [apiData, apiName, tempApiData.apiName, updater]);
React.useEffect(() => {
if (!pathParams) return;
const prefixPath = !apiData ? ['paths', apiName] : [];
const tempDetail = produce(openApiDoc, (draft) => {
if (!isEmpty(pathParams)) {
const _pathParams = map(pathParams, (name) => {
return {
...DEFAULT_PATH_PARAM,
name,
};
});
set(draft, [...prefixPath, 'parameters'], _pathParams);
} else {
set(draft, [...prefixPath, 'parameters'], []);
updater.pathParams(null);
}
});
updateOpenApiDoc(tempDetail);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [apiData, apiName, pathParams, updateOpenApiDoc]);
const setFieldHandle = React.useCallback(
(key: string, fieldData: Obj, extraProps?: Obj) => {
const prefixPath = !apiData?.apiMethod ? ['paths', ...dataPath] : [];
const baseFormData = !apiData?.apiMethod ? openApiDoc : tempApiData;
if (onQuoteChange && extraProps?.typeQuotePath) {
const newTypeQuotePath = extraProps?.typeQuotePath ? ['paths', ...dataPath, ...extraProps.typeQuotePath] : [];
const tempQuotePathMap = produce(quotePathMap, (draft) => {
if (extraProps?.quoteTypeName) {
const { quoteTypeName } = extraProps;
draft[quoteTypeName] = draft[quoteTypeName] || [];
draft[quoteTypeName].push(newTypeQuotePath);
}
});
onQuoteChange(tempQuotePathMap);
}
const tempDetail = produce(baseFormData, (draft) => {
if (key !== 'responses') {
const responseData = get(draft, [...prefixPath, 'responses']);
// 设置默认的response
if (!responseData) {
set(draft, [...prefixPath, 'responses'], DEFAULT_RESPONSE);
}
if (key === 'summary') {
set(draft, [...prefixPath, fieldData?.propertyName], fieldData?.propertyData);
if (fieldData?.propertyName === 'operationId') {
set(draft, [...prefixPath, 'summary'], fieldData?.propertyData);
}
if (fieldData?.newTags) {
set(draft, 'tags', fieldData?.newTags);
message.success(i18n.t('dop:category created successfully'));
}
} else if (key === 'query' || key === 'header') {
set(draft, [...prefixPath, 'parameters'], fieldData?.parameters);
}
}
if (key === 'responses' || key === 'requestBody') {
set(draft, [...prefixPath, key], fieldData[key]);
}
// 设置默认的operationId
if (!get(draft, [...prefixPath, 'operationId'])) {
const _operationIdList: string[] = [];
const { paths } = openApiDoc;
forEach(keys(paths), (pathName: string) => {
const methodData = paths[pathName];
forEach(keys(methodData), (item) => {
methodData[item]?.operationId && _operationIdList.push(methodData[item]?.operationId);
});
});
let _operationId = 'operationId';
while (_operationIdList.includes(_operationId)) {
_operationId += '1';
}
set(draft, [...prefixPath, 'operationId'], _operationId);
}
// 设置默认的tags
if (!get(draft, [...prefixPath, 'tags'])) {
set(draft, [...prefixPath, 'tags'], ['other']);
}
if (!draft.tags) {
set(draft, 'tags', [{ name: 'other' }]);
}
});
if (!apiData?.apiMethod) {
updateOpenApiDoc(tempDetail);
} else {
updater.tempApiData(tempDetail);
}
},
[apiData, dataPath, onQuoteChange, openApiDoc, quotePathMap, tempApiData, updateOpenApiDoc, updater],
);
const iconClassMap = React.useMemo(() => {
const classMap = {};
const emptyIcon = {};
forEach(API_METHODS, (method) => {
const tempMethodDetail = get(openApiDoc, ['paths', apiName, method]);
const emptyMethodClass = !tempMethodDetail || isEmpty(tempMethodDetail) ? 'btn-icon-empty' : '';
classMap[method] = `btn-icon btn-icon-${method} ${emptyMethodClass}`;
emptyIcon[method] = `${emptyMethodClass}`;
});
return { classMap, emptyIcon };
}, [apiName, openApiDoc]);
const deleteMethod = React.useCallback(
(methodKey: API_METHODS) => {
updater.open(false);
const tempDetail = produce(openApiDoc, (draft) => {
unset(draft, ['paths', apiName, methodKey]);
});
updateOpenApiDoc(tempDetail);
if (currentMethod === methodKey) {
updater.apiMethodDetail({});
}
},
[apiName, currentMethod, openApiDoc, updateOpenApiDoc, updater],
);
const onApiNameChange = React.useCallback(
(name: string) => {
updater.apiDisplayName(name);
props.onApiNameChange(name);
// 获取api中的path parameters
const _pathParams = map(name.match(pathParamReg), (item) => {
return item.slice(1, item.length - 1);
});
updater.pathParams(_pathParams);
if (onQuoteChange && !isEmpty(quotePathMap)) {
const tempQuotePathMap = produce(quotePathMap, (draft) => {
forEach(keys(draft), (k) => {
forEach(draft[k], (path, i) => {
if (path.includes(apiDisplayName)) {
const oldPathArray = path.slice(0, path.length - 1);
draft[k][i] = [...oldPathArray, name];
}
});
});
});
onQuoteChange(tempQuotePathMap);
}
if (!apiData?.apiMethod) {
const tempDetail = produce(openApiDoc, (draft) => {
const apiTempData = get(draft, ['paths', apiName]);
set(draft, ['paths', name], apiTempData);
unset(draft, ['paths', apiName]);
});
updateOpenApiDoc(tempDetail);
} else {
const tempDetail = produce(tempApiData, (draft) => {
set(draft, 'apiName', name);
});
updater.tempApiData(tempDetail);
}
},
[
apiData,
apiDisplayName,
apiName,
onQuoteChange,
openApiDoc,
props,
quotePathMap,
tempApiData,
updateOpenApiDoc,
updater,
],
);
const hasBody = !['get', 'head'].includes(currentMethod);
const onSaveApiData = React.useCallback(() => {
execOperation &&
execOperation(operations.submit, {
apiData: { ...tempApiData, apiMethod: apiData?.apiMethod, apiName: tempApiData?.name || apiData?.apiName },
});
}, [apiData, execOperation, operations, tempApiData]);
const fieldList = React.useMemo(() => {
const existApiPathNames = keys(openApiDoc?.paths).filter((n) => n !== apiDisplayName);
return [
{
type: Input,
name: 'apiName',
colSpan: 24,
required: false,
isHoldLabel: false,
wrapperClassName: 'pl-0',
customProps: {
className: 'name-input',
maxLength: INPUT_MAX_LENGTH,
disabled: apiLockState,
addonBefore: apiData?.apiMethod,
placeholder: i18n.t('Please enter the {name}', { name: i18n.t('API path') }),
onChange: (e: React.ChangeEvent<HTMLInputElement>) => {
const newApiName = e.target.value;
if (newApiName && !existApiPathNames.includes(newApiName) && newApiName.startsWith('/')) {
onApiNameChange(newApiName);
updateFormErrorNum(0);
} else {
updateFormErrorNum(1);
}
},
},
rules: [
{
validator: (_rule: any, value: string, callback: (msg?: string) => void) => {
if (existApiPathNames.includes(value)) {
callback(i18n.t('the same {key} exists', { key: i18n.t('Name') }));
} else if (!value) {
callback(i18n.t('can not be empty'));
} else if (!value.startsWith('/')) {
callback(i18n.t('dop:path must start with /'));
} else {
callback();
}
},
},
],
},
];
}, [apiData, apiDisplayName, apiLockState, onApiNameChange, openApiDoc, updateFormErrorNum]);
const popconfirmRef = React.useRef(null as any);
const selectRef = React.useRef(null) as any;
useClickAway(selectRef, () => {
updater.open(false);
});
const maskClick = React.useCallback(
(e: any) => {
e.stopPropagation();
updater.open(true);
},
[updater],
);
const labelClick = React.useCallback(
(e: any, methodKey: string) => {
e.stopPropagation();
updater.open(false);
const nextHandle = () => {
const _apiMethodDetail = get(openApiDoc, ['paths', apiName, methodKey]) || {};
update({
currentMethod: methodKey as API_SETTING.ApiMethod,
curTabKey: API_RESOURCE_TAB.Summary,
apiMethodDetail: _apiMethodDetail,
});
updateFormErrorNum(0);
formRef.current.setFieldsValue({ apiName });
};
if (formErrorNum > 0) {
confirm({
title: i18n.t('dop:Are you sure to leave, with the error message not saved?'),
onOk() {
nextHandle();
},
});
} else {
nextHandle();
}
},
[apiName, formErrorNum, openApiDoc, update, updateFormErrorNum, updater],
);
const renderSelectMenu = () => {
return (
<div className="select-container" ref={selectRef}>
{!apiData?.apiMethod ? (
<Select
getPopupContainer={(triggerNode) => triggerNode.parentElement as HTMLElement}
style={{ marginRight: '8px', width: '141px' }}
defaultValue={currentMethod}
open={open}
value={currentMethod}
>
{map(API_METHODS, (methodKey) => {
let item = (
<div className="circle-container flex-all-center">
{iconClassMap.emptyIcon[methodKey] ? (
<div className={`${iconClassMap.classMap[methodKey]}`} />
) : (
<ErdaIcon type="check" className={iconClassMap.classMap[methodKey]} />
)}
</div>
);
if (get(openApiDoc, ['paths', apiName, methodKey])) {
item = (
<Popconfirm
title={`${i18n.t('common:confirm to delete')}?`}
onConfirm={() => deleteMethod(methodKey)}
placement="right"
// disabled={apiLockState}
overlayClassName="popconfirm-container"
getPopupContainer={() => popconfirmRef?.current}
onCancel={(e: any) => {
e.stopPropagation();
updater.open(false);
}}
>
{item}
</Popconfirm>
);
}
return (
<Option value={methodKey} key={methodKey}>
<div
className={`api-method-option ${currentMethod === methodKey ? 'api-method-option-active' : ''}`}
key={methodKey}
>
<div
onClick={(e) => {
e.stopPropagation();
labelClick(e, methodKey);
}}
>
{methodKey.toUpperCase()}
</div>
{item}
</div>
</Option>
);
})}
</Select>
) : undefined}
<div className="mask" onClick={maskClick} />
</div>
);
};
const onTabChange = (tabKey: string) => {
const nextHandle = () => {
updater.curTabKey(tabKey as API_RESOURCE_TAB);
const _apiMethodDetail = get(openApiDoc, ['paths', apiName, currentMethod]) || {};
updater.apiMethodDetail(_apiMethodDetail);
updateFormErrorNum(0);
formRef.current.setFieldsValue({ apiName });
};
if (formErrorNum > 0) {
confirm({
title: i18n.t('dop:Are you sure to leave, with the error message not saved?'),
onOk() {
nextHandle();
},
});
} else {
nextHandle();
}
};
return (
<div className="api-resource" ref={popconfirmRef}>
<div className="popover">
{renderSelectMenu()}
<FormBuilder ref={formRef} className="w-full">
<Fields fields={fieldList} />
</FormBuilder>
</div>
<div className="api-resource-tabs">
<Tabs activeKey={curTabKey} onChange={onTabChange}>
<TabPane tab={API_RESOURCE_TAB.Summary} key={API_RESOURCE_TAB.Summary}>
<ResourceSummary formData={apiMethodDetail} onChange={setFieldHandle} isEditMode={!apiLockState} />
</TabPane>
<TabPane tab={API_RESOURCE_TAB.Params} key={API_RESOURCE_TAB.Params}>
<QueryParamsConfig
formData={apiMethodDetail}
paramIn="query"
onChange={setFieldHandle}
isEditMode={!apiLockState}
resourceKey={curTabKey}
/>
</TabPane>
<TabPane tab={API_RESOURCE_TAB.Headers} key={API_RESOURCE_TAB.Headers}>
<QueryParamsConfig
formData={apiMethodDetail}
paramIn="header"
onChange={setFieldHandle}
isEditMode={!apiLockState}
resourceKey={curTabKey}
/>
</TabPane>
<TabPane tab={API_RESOURCE_TAB.Body} key={API_RESOURCE_TAB.Body} disabled={!hasBody}>
{hasBody && (
<ResponseConfig
formData={apiMethodDetail}
paramIn="requestBody"
onChange={setFieldHandle}
dataPath={dataPath}
isEditMode={!apiLockState}
resourceKey={curTabKey}
/>
)}
</TabPane>
<TabPane tab={API_RESOURCE_TAB.Response} key={API_RESOURCE_TAB.Response}>
<ResponseConfig
formData={apiMethodDetail}
paramIn="responses"
onChange={setFieldHandle}
dataPath={dataPath}
isEditMode={!apiLockState}
resourceKey={curTabKey}
/>
</TabPane>
{apiData?.apiMethod && <TabPane tab={API_RESOURCE_TAB.Test} key={API_RESOURCE_TAB.Test} />}
</Tabs>
{apiData?.apiMethod && (
<div className="flex items-center flex-wrap justify-end">
<Button type="primary" onClick={onSaveApiData}>
{i18n.t('Save')}
</Button>
</div>
)}
</div>
</div>
);
}
Example #9
Source File: record-detail.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
Info = ({ appId }: { appId: string }) => {
const [{ isExpand }, updater] = useUpdate({
isExpand: false,
});
const toggleContainer: React.RefObject<HTMLDivElement> = React.useRef(null);
const commitMsgRef: React.RefObject<HTMLDivElement> = React.useRef(null);
const cronMsgRef: React.RefObject<HTMLDivElement> = React.useRef(null);
const style = `main-info ${isExpand ? 'main-info-full' : ''}`;
const [pipelineDetail] = buildStore.useStore((s) => [s.pipelineDetail]);
useClickAway(toggleContainer, () => {
updater.isExpand(false);
});
if (!pipelineDetail) return null;
const { id: pipelineID, pipelineCron, costTimeSec = -1, commit, commitDetail } = pipelineDetail;
const { cronExpr } = pipelineCron;
const cronMsg = cronExpr && cronstrue.toString(cronExpr, { locale: isZh() ? 'zh_CN' : 'en' });
const getAutoTooltipMsg = (ref: any, text: any) => {
// show tooltip only when text overflow
const { current = {} } = ref;
if (current != null && current.scrollWidth > current.clientWidth) {
return <Tooltip title={text}>{text}</Tooltip>;
}
return text;
};
const toggleExpandInfo = (event: any) => {
event.stopPropagation();
updater.isExpand(!isExpand);
};
return pipelineDetail ? (
<div className="main-info-parent">
<div className={style} ref={toggleContainer}>
<Row className="mb-4">
<Col span={12}>
{commitDetail?.author ? <Avatar name={commitDetail.author} showName className="mb-1" size={20} /> : '-'}
<div className="info-label">{i18n.t('Submitter')}:</div>
</Col>
<Col span={12}>
<div className="nowrap" ref={commitMsgRef}>
{getAutoTooltipMsg(commitMsgRef, replaceEmoji(commitDetail?.comment || '')) || '-'}
</div>
<div className="info-label">{firstCharToUpper(i18n.t('dop:commit message'))}:</div>
</Col>
</Row>
<Row className="mb-4">
<Col span={12}>
<div className="hover-py">{commit ? <GotoCommit length={6} commitId={commit} appId={appId} /> : '-'}</div>
<div className="info-label">{i18n.t('Commit')} ID:</div>
</Col>
<Col span={12}>
{commitDetail?.time ? moment(new Date(commitDetail.time)).format('YYYY-MM-DD HH:mm:ss') : '-'}
<div className="info-label">{i18n.t('commit date')}:</div>
</Col>
</Row>
<Row className="mb-4">
<Col span={12}>
{costTimeSec !== -1 ? `${i18n.t('dop:time cost')} ${secondsToTime(+costTimeSec)}` : '-'}
<div className="info-label">{i18n.t('Duration')}:</div>
</Col>
<Col span={12}>
{pipelineID || '-'}
<div className="info-label">{i18n.t('Pipeline')} ID:</div>
</Col>
</Row>
<Row className="mb-4">
{cronMsg && (
<Col span={12}>
<div className="nowrap" ref={cronMsgRef}>
{getAutoTooltipMsg(cronMsgRef, cronMsg) || '-'}
</div>
<div className="info-label">{i18n.t('timing time')}:</div>
</Col>
)}
</Row>
<div className="trigger-btn" onClick={toggleExpandInfo}>
{!isExpand ? (
<ErdaIcon type="down" size="18px" className="mr-0" />
) : (
<ErdaIcon type="up" size="18px" className="mr-0" />
)}
</div>
</div>
</div>
) : null;
}
Example #10
Source File: index.tsx From erda-ui with GNU Affero General Public License v3.0 | 4 votes |
LandPage = () => {
const filteredList = orgStore.useStore((s) => s.orgs);
const loginUser = userStore.useStore((s) => s.loginUser);
const { getJoinedOrgs } = orgStore.effects;
const [activeOrg, setActiveOrg] = React.useState<any>(null);
const [showOptions, setShowOptions] = React.useState(false);
const [filterKey, setFilterKey] = React.useState('');
const [modalVisible, setModalVisible] = React.useState(false);
const debouncedChange = React.useRef(debounce(getJoinedOrgs, 1000));
const ref = React.useRef(null);
useClickAway(ref, () => {
setShowOptions(false);
});
React.useEffect(() => {
debouncedChange.current({ q: filterKey, force: true });
}, [filterKey]);
const getFieldsList = (form: WrappedFormUtils) => {
const fieldsList = [
{
label: i18n.t('layout:Organization name'),
name: 'displayName',
itemProps: {
onInput: (e: any) => {
let v = e.target.value.trim();
if (pinyin.isSupported()) {
v = pinyin.convertToPinyin(v, '', true);
}
form.setFieldsValue({
name: v.split(' ').join('-').toLowerCase(),
});
form.validateFields(['name']);
},
},
},
{
label: i18n.t('layout:Organization identifier'),
name: 'name',
itemProps: {
maxLength: 50,
},
rules: [
{
required: true,
message: i18n.t('Please enter the {name}', { name: i18n.t('layout:Organization identifier') }),
},
{
pattern: /^[a-z0-9-]*$/,
message: i18n.t('layout:only allowed to consist of lower case characters, numbers and -'),
},
],
},
{
label: i18n.t('layout:org logo'),
name: 'logo',
required: false,
getComp: () => <ImageUpload id="logo" form={form} showHint />,
},
{
label: i18n.t('layout:Organization description'),
name: 'desc',
itemProps: {
type: 'textarea',
maxLength: 500,
},
},
];
return fieldsList;
};
const onSaveOrg = (org: Partial<ORG.IOrg>) => {
addOrg.fetch({ ...org, type: 'FREE', admins: [loginUser.id] }).then(() => {
hideAddOrgModal();
debouncedChange.current({ q: filterKey, force: true });
message.success(i18n.t('layout:created successfully, please check in your org space'));
});
};
const hideAddOrgModal = () => setModalVisible(false);
return (
<div className="land-page flex items-center justify-center h-full">
<div className="absolute flex items-center justify-between left-20 right-20 top-5 z-10">
<ErdaIcon className="text-white" size={60} type="erda" />
<UserMenu placement="bottomRight" size={36} align={{ offset: [0, -6] }} className="no-arrow" />
</div>
<img className="bg-image" src={springBg} alt="background-image" />
<div className="content text-white z-10">
<div className="title">
<div>{i18n.t('layout:On the Cloud')}</div>
<div>{i18n.t('layout:Collaborative Application Development Platform')}</div>
</div>
<div className="mt-8 org-select-text">{i18n.t('layout:Choose your organization space')}</div>
<div
ref={ref}
className={`mt-4 rounded-sm h-16 py-5 text-default cursor-pointer flex items-center justify-between org-select ${
showOptions ? 'showOptions' : ''
} ${filterKey ? 'searching' : ''}`}
>
<input
className="input"
type="text"
value={activeOrg?.displayName || filterKey}
onChange={(e) => !activeOrg && setFilterKey(e.target.value)}
onClick={(e) => setShowOptions(true)}
/>
<div className="tip text-default-6">{i18n.t('layout:Organizational space')}</div>
<ErdaIcon className="icon mr-6" size={20} type="caret-down" />
<div className="options">
{filteredList.length ? (
filteredList.map((org) => {
return (
<a
key={org.id}
href={`/${org.name}`}
className={`option flex items-center px-2 h-[76px] cursor-pointer hover:bg-default-04 ${
org.id === activeOrg?.id ? 'active' : ''
}`}
onMouseEnter={() => setActiveOrg(org)}
onMouseLeave={() => setActiveOrg(null)}
>
{org.logo ? (
<img className="w-10 h-10 rounded-sm" src={org.logo} alt={`${org.name} logo`} />
) : (
<ErdaIcon type="zuzhi-40k0k60g" size={40} />
)}
<div className="ml-2 flex-1 truncate">
<div className="org-name truncate">{org.displayName}</div>
<div className="org-sub-name text-xs text-desc truncate">{org.desc}</div>
</div>
</a>
);
})
) : (
<div className="h-full flex-all-center">
<ErdaIcon type="zuzhi-40k0k60g" size={64} />
<div>
<div className="org-name">
{filterKey ? i18n.t('No matching organization') : i18n.t("Haven't join any org")}
</div>
<div className="org-sub-name text-xs text-desc">
{filterKey
? i18n.t('Search results are empty')
: i18n.t('Contact your organization administrator to invite you to join')}
</div>
</div>
</div>
)}
</div>
</div>
{erdaEnv.ENABLE_APPLY_ORG && (
<a
className="inline-block mt-6 mr-2 px-3 leading-8 text-white bg-white-2 rounded-sm cursor-pointer hover:bg-white-4"
href="https://www.erda.cloud/contact"
target="_blank"
rel="noreferrer"
>
{i18n.t('layout:Apply for new organization')}
</a>
)}
{process.env.FOR_COMMUNITY && (
<span
className="inline-block mt-6 px-3 leading-8 text-white bg-white-2 rounded-sm cursor-pointer hover:bg-white-4"
onClick={() => setModalVisible(true)}
>
{i18n.t('layout:Create new organization')}
</span>
)}
</div>
<FormModal
width={600}
name={i18n.t('org')}
fieldsList={getFieldsList}
modalProps={{
destroyOnClose: true,
}}
visible={modalVisible}
onOk={onSaveOrg}
onCancel={hideAddOrgModal}
/>
</div>
);
}
Example #11
Source File: inline-value.tsx From platyplus with MIT License | 4 votes |
InlineValue: React.FC<{
editable?: boolean
value: string
label?: string
onChange: (value: string) => void
}> = ({ editable = true, value, label, onChange }) => {
// * Toggle title editing
const [editing, toggle] = useToggle(false)
const readValue = useMemo(() => label ?? value, [value, label])
// * Input value state
const [inputValue, setInputValue] = useState('')
useEffect(() => {
setInputValue(value)
}, [value])
// * Cancel title editing
const cancel = () => {
toggle(false)
setInputValue(value)
}
// * Set title into the config store
const validate = () => {
toggle(false)
onChange(inputValue)
}
// * If editing and clicking away, stop editing
const ref = useRef(null)
useClickAway(ref, cancel)
// * Adjust Input width automatically
// TODO width does not scale correctly with value length
// * See useSize in react-use
const [width, setWidth] = useState(`${14 + value?.length}ch`)
useEffect(() => setWidth(`${14 + inputValue?.length}ch`), [inputValue])
return (
<Animation.Fade in={!!value}>
{(props) => (
<span {...props}>
{editing ? (
<div
ref={ref}
style={{
width,
display: 'inline-block'
}}
>
<InputGroup size="xs">
<Input
size="xs"
value={inputValue}
onChange={setInputValue}
onKeyDown={({ key }) => key === 'Enter' && validate()}
autoFocus
/>
<InputGroup.Button size="xs" onClick={cancel}>
<Icon icon="close" />
</InputGroup.Button>
<InputGroup.Button size="xs" onClick={validate}>
<Icon icon="check" />
</InputGroup.Button>
</InputGroup>
</div>
) : editable ? (
<span onClick={toggle}>
{readValue ? (
readValue
) : (
<IconButton size="xs" icon={<Icon icon="edit" />}>
Edit template
</IconButton>
)}
</span>
) : (
<span>{readValue}</span>
)}
</span>
)}
</Animation.Fade>
)
}
Example #12
Source File: AddInput.tsx From nextjs-hasura-fullstack with MIT License | 4 votes |
AddInput: React.FC<AddInputProps> = (props) => {
const {
onSubmit,
isCreating,
setIsCreating,
loading,
className,
label,
placeholder,
onClickAway,
} = props
const [value, setValue] = React.useState('')
const reset = React.useCallback(() => {
setIsCreating(false)
setValue('')
}, [])
const handleClickAway = React.useCallback(() => {
onClickAway(value)
reset()
}, [onClickAway, reset, value])
const ref = React.useRef<HTMLInputElement>(null)
useClickAway(ref, () => handleClickAway())
useKey('Escape', () => handleClickAway())
const handleSubmit = React.useCallback(() => {
onSubmit(value)
reset()
}, [onSubmit, reset, value])
const cls = clsx(className, `flex w-full h-10`)
return (
<div className={cls}>
{!isCreating && (
<Button
isLoading={loading}
isBlock
className={``}
onClick={() => {
setIsCreating(true)
}}
variant="light"
iconLeft={
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z"
clipRule="evenodd"
/>
</svg>
}
>
{label}
</Button>
)}
{isCreating && (
<div className={`flex items-center justify-between`} ref={ref}>
<Input
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={true}
placeholder={placeholder ?? `New ${label}`}
value={value}
onChange={(e) => {
setValue(e.target.value)
}}
className={``}
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleSubmit()
}
}}
/>
<ButtonIcon
className={`flex-grow ml-2`}
size="base"
onClick={(e) => {
e.preventDefault()
handleSubmit()
}}
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M5 13l4 4L19 7"
/>
</svg>
}
/>
</div>
)}
</div>
)
}