From 4b4eb2dfd793cafa56ba9376a7525a25245bd48e Mon Sep 17 00:00:00 2001 From: Wroud Date: Sun, 17 May 2020 15:07:21 +0300 Subject: [PATCH] refactor(authentication): reuse Tab block refactor(custom-connection-plugin): reuse Tab block --- .../authentication/src/Dialog/AuthDialog.tsx | 120 +++++++---------- webapp/packages/core/assets/styles/_tabs.scss | 90 +++++++------ .../packages/core/src/blocks/Tabs/Tab/Tab.tsx | 22 ++-- .../packages/core/src/blocks/Tabs/TabList.tsx | 22 ++++ .../core/src/blocks/Tabs/TabsBox/TabsBox.tsx | 18 +-- .../core/src/blocks/Tabs/TabsState.tsx | 40 ++++++ webapp/packages/core/src/blocks/Tabs/index.ts | 2 + .../ConnectionForm/ConnectionForm.tsx | 4 +- .../ConnectionForm/formStyles.ts | 4 + .../ConnectionFormDialog.tsx | 123 ++++++++---------- .../DriverProperties/DriverProperties.tsx | 9 +- 11 files changed, 245 insertions(+), 209 deletions(-) create mode 100644 webapp/packages/core/src/blocks/Tabs/TabList.tsx create mode 100644 webapp/packages/core/src/blocks/Tabs/TabsState.tsx diff --git a/webapp/packages/authentication/src/Dialog/AuthDialog.tsx b/webapp/packages/authentication/src/Dialog/AuthDialog.tsx index 2823f8bd67..202f3c5c85 100644 --- a/webapp/packages/authentication/src/Dialog/AuthDialog.tsx +++ b/webapp/packages/authentication/src/Dialog/AuthDialog.tsx @@ -7,12 +7,11 @@ */ import { observer } from 'mobx-react'; -import { - useTabState, Tab as BaseTab, TabList -} from 'reakit/Tab'; import styled, { css } from 'reshadow'; -import { SubmittingForm, ErrorMessage } from '@dbeaver/core/blocks'; +import { + SubmittingForm, ErrorMessage, TabsState, TabList, Tab, TabTitle +} from '@dbeaver/core/blocks'; import { useController } from '@dbeaver/core/di'; import { DialogComponent, CommonDialogWrapper } from '@dbeaver/core/dialogs'; import { useTranslate } from '@dbeaver/core/localization'; @@ -24,7 +23,7 @@ import { AuthProviderForm } from './AuthProviderForm/AuthProviderForm'; const styles = composes( css` - BaseTab { + Tab { composes: theme-ripple theme-background-secondary theme-text-on-secondary from global; } ErrorMessage { @@ -53,34 +52,19 @@ const styles = composes( flex-direction: column; } - BaseTab { - outline: none; - } - TabList { box-sizing: border-box; display: inline-flex; width: 100%; padding-left: 24px; } - BaseTab { + Tab { composes: theme-typography--body2 from global; text-transform: uppercase; - padding: 12px 16px; - border-top: solid 2px transparent; - height: 48px; + font-weight: normal; - &:global([aria-selected='true']) { - border-top-color: #fd1d48; - - &:before { - display: none; - } - } - - &:not(:global([aria-selected='true'])) { - cursor: pointer; - background-color: transparent !important; + &:global([aria-selected=true]) { + font-weight: normal !important; } } AuthProviderForm { @@ -99,56 +83,52 @@ export const AuthDialog: DialogComponent = observer( function AuthDialog(props) { const controller = useController(AuthDialogController, props.rejectDialog); const translate = useTranslate(); - const tab = useTabState({ - selectedId: controller.provider?.id, - }); - tab.selectedId = controller.provider?.id || null; return styled(useStyles(styles))( - - {controller.providers.map(provider => ( - controller.selectProvider(provider.id)} - > - {provider.label} - - ))} - - )} - footer={( - - )} - onReject={props.options?.persistent ? undefined : props.rejectDialog} - > - - {controller.provider && ( - + + {controller.providers.map(provider => ( + controller.selectProvider(provider.id)} + > + + + ))} + + )} + footer={( + )} - {!controller.provider && <>Select available provider} - - {controller.error.responseMessage && ( - - )} - + onReject={props.options?.persistent ? undefined : props.rejectDialog} + > + + {controller.provider && ( + + )} + {!controller.provider && <>Select available provider} + + {controller.error.responseMessage && ( + + )} + + ); } ); diff --git a/webapp/packages/core/assets/styles/_tabs.scss b/webapp/packages/core/assets/styles/_tabs.scss index fc2c6a9e92..7c211c017e 100644 --- a/webapp/packages/core/assets/styles/_tabs.scss +++ b/webapp/packages/core/assets/styles/_tabs.scss @@ -36,19 +36,15 @@ Tab { composes: theme-typography--body2 from global; position: relative; - display: flex; - flex-shrink: 0; - text-align: left; - align-items: center; outline: none; font-weight: normal; cursor: pointer; + padding: 0; height: 48px; - padding: 0 24px 0 12px; border-top: solid 2px transparent; - &:global([aria-selected=true]) { + &:global([aria-selected="true"]) { font-weight: 500; cursor: auto; border-top-color: $color-negative; @@ -58,52 +54,64 @@ } } - &:not(:global([aria-selected=true])) { + &:not(:global([aria-selected="true"])) { background-color: transparent !important; } } - tab-icon { - height: 22px; - padding-right: 8px; + tab-container { + height: 100%; + display: flex; + flex-shrink: 0; + text-align: left; + align-items: center; - & StaticImage, - & img { - width: 22px; - } - - & placeholder { - width: 22px; + tab-icon { height: 22px; + padding-left: 12px; + + StaticImage, + img { + width: 22px; + } + + placeholder { + width: 22px; + height: 22px; + } } - } - tab-title placeholder { - width: 80px; - height: 16px; - } + tab-title { + padding: 0 12px; + } - tab-action:before { - content: ""; - display: block; - left: -2px; - top: -2px; - height: 12px; - width: 12px; - position: absolute; - cursor: pointer; - } + tab-title placeholder { + width: 80px; + height: 16px; + } - tab-action { - height: 8px; - width: 8px; - align-self: flex-start; - position: absolute; - top: 4px; - right: 4px; - - & Icon { + tab-action:before { + content: ""; display: block; + left: -4px; + top: 0; + height: 16px; + width: 16px; + position: absolute; + cursor: pointer; + } + + tab-action { + position: relative; + height: 8px; + width: 8px; + align-self: flex-start; + padding: 4px; + padding-left: 0; + + Icon { + display: block; + } } } diff --git a/webapp/packages/core/src/blocks/Tabs/Tab/Tab.tsx b/webapp/packages/core/src/blocks/Tabs/Tab/Tab.tsx index e867c223eb..aa90c68c4e 100644 --- a/webapp/packages/core/src/blocks/Tabs/Tab/Tab.tsx +++ b/webapp/packages/core/src/blocks/Tabs/Tab/Tab.tsx @@ -18,7 +18,7 @@ import { TabsContext } from '../TabsContext'; type TabProps = PropsWithChildren<{ tabId: string; className?: string; - onOpen: (tabId: string) => void; + onOpen?: (tabId: string) => void; onClose?: (tabId: string) => void; }> @@ -36,8 +36,10 @@ export function Tab({ const handleOpen = useCallback((e: React.MouseEvent) => { e.preventDefault(); - onOpen(tabId); - }, []); + if (onOpen) { + onOpen(tabId); + } + }, [onOpen]); const handleClose = useCallback((e: React.MouseEvent) => { e.preventDefault(); @@ -54,12 +56,14 @@ export function Tab({ className={className} onClick={handleOpen} > - {children} - {onClose && ( - - - - )} + + {children} + {onClose && ( + + + + )} + ); } diff --git a/webapp/packages/core/src/blocks/Tabs/TabList.tsx b/webapp/packages/core/src/blocks/Tabs/TabList.tsx new file mode 100644 index 0000000000..7e4744743a --- /dev/null +++ b/webapp/packages/core/src/blocks/Tabs/TabList.tsx @@ -0,0 +1,22 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { useContext } from 'react'; +import { TabList as BaseTabList, TabListOptions } from 'reakit/Tab'; + +import { TabsContext } from './TabsContext'; + +export function TabList(props: React.PropsWithChildren) { + const state = useContext(TabsContext); + + if (!state) { + throw new Error('Tabs context was not provided'); + } + + return ; +} diff --git a/webapp/packages/core/src/blocks/Tabs/TabsBox/TabsBox.tsx b/webapp/packages/core/src/blocks/Tabs/TabsBox/TabsBox.tsx index b13f3b9265..98f43cb4f1 100644 --- a/webapp/packages/core/src/blocks/Tabs/TabsBox/TabsBox.tsx +++ b/webapp/packages/core/src/blocks/Tabs/TabsBox/TabsBox.tsx @@ -6,15 +6,12 @@ * you may not use this file except in compliance with the License. */ -import { - useMemo, PropsWithChildren, ReactNode, useEffect, -} from 'react'; -import { useTabState } from 'reakit/Tab'; +import { PropsWithChildren, ReactNode } from 'react'; import styled from 'reshadow'; import { useStyles, Style } from '@dbeaver/core/theming'; -import { TabsContext } from '../TabsContext'; +import { TabsState } from '../TabsState'; type TabsBoxProps = PropsWithChildren<{ currentTabId: string; @@ -30,16 +27,9 @@ export function TabsBox({ className, style = [], }: TabsBoxProps) { - const state = useTabState({ - selectedId: currentTabId, - manual: true, - }); - state.selectedId = currentTabId; - useEffect(() => state.select(currentTabId), [currentTabId]); // hack currentId and selectedId not works - const value = useMemo(() => state, Object.values(state)); return styled(useStyles(...style))( - + {tabs && ( @@ -50,6 +40,6 @@ export function TabsBox({ {children} - + ); } diff --git a/webapp/packages/core/src/blocks/Tabs/TabsState.tsx b/webapp/packages/core/src/blocks/Tabs/TabsState.tsx new file mode 100644 index 0000000000..b0ae6738dc --- /dev/null +++ b/webapp/packages/core/src/blocks/Tabs/TabsState.tsx @@ -0,0 +1,40 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { useEffect, useMemo } from 'react'; +import { useTabState } from 'reakit/Tab'; + +import { TabsContext } from './TabsContext'; + +type Props = React.PropsWithChildren<{ + selectedId?: string; + currentTabId?: string | null; +}> + +export function TabsState({ selectedId, currentTabId, children }: Props) { + const state = useTabState({ + selectedId: selectedId || currentTabId, + manual: true, + }); + if (currentTabId) { + state.selectedId = currentTabId; + } + useEffect(() => { + if (!currentTabId) { + return; + } + state.select(currentTabId); + }, [currentTabId]); // hack currentId and selectedId not works + const value = useMemo(() => state, Object.values(state)); + + return ( + + {children} + + ); +} diff --git a/webapp/packages/core/src/blocks/Tabs/index.ts b/webapp/packages/core/src/blocks/Tabs/index.ts index b0231505c1..683b15242a 100644 --- a/webapp/packages/core/src/blocks/Tabs/index.ts +++ b/webapp/packages/core/src/blocks/Tabs/index.ts @@ -1,6 +1,8 @@ export * from './Tab'; export * from './TabPanel'; export * from './TabsContext'; +export * from './TabsState'; +export * from './TabList'; export * from './ITab'; diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ConnectionForm.tsx b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ConnectionForm.tsx index afa625d03e..d1394ae450 100644 --- a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ConnectionForm.tsx +++ b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ConnectionForm.tsx @@ -32,7 +32,7 @@ export const ConnectionForm = observer(function ConnectionForm({ const translate = useTranslate(); return styled(useStyles(formStyles))( - <> + )} {!driver?.anonymousAccess && } - + ); }); diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionForm/formStyles.ts b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionForm/formStyles.ts index ccc9a48c55..001af5123b 100644 --- a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionForm/formStyles.ts +++ b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionForm/formStyles.ts @@ -9,6 +9,10 @@ import { css } from 'reshadow'; export const formStyles = css` + connection-form { + flex-direction: column; + padding: 18px 24px; + } connection-type { padding: 12px; } diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialog.tsx b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialog.tsx index fae0634462..cef1038a22 100644 --- a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialog.tsx +++ b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/ConnectionFormDialog.tsx @@ -7,13 +7,13 @@ */ import { observer } from 'mobx-react'; -import { - useTabState, Tab as BaseTab, TabList, TabPanel as BaseTabPanel, -} from 'reakit/Tab'; +import { useState } from 'react'; import styled, { css } from 'reshadow'; import { DBDriver } from '@dbeaver/core/app'; -import { SubmittingForm, ErrorMessage } from '@dbeaver/core/blocks'; +import { + SubmittingForm, ErrorMessage, TabsState, TabList, Tab, TabTitle, TabPanel +} from '@dbeaver/core/blocks'; import { useController } from '@dbeaver/core/di'; import { CommonDialogWrapper } from '@dbeaver/core/dialogs'; import { useTranslate } from '@dbeaver/core/localization'; @@ -26,7 +26,7 @@ import { DriverProperties } from './DriverProperties/DriverProperties'; const styles = composes( css` - BaseTab { + Tab { composes: theme-ripple theme-background-secondary theme-text-on-secondary from global; } ErrorMessage { @@ -52,39 +52,20 @@ const styles = composes( flex-direction: column; } - BaseTab, BaseTabPanel { - outline: none; - } - TabList { box-sizing: border-box; display: inline-flex; width: 100%; padding-left: 24px; } - BaseTab { + Tab { composes: theme-typography--body2 from global; text-transform: uppercase; - padding: 12px 16px; - border-top: solid 2px transparent; - height: 48px; + font-weight: normal; - &:global([aria-selected='true']) { - border-top-color: #fd1d48; - - &:before { - display: none; - } + &:global([aria-selected=true]) { + font-weight: normal !important; } - - &:not(:global([aria-selected='true'])) { - cursor: pointer; - background-color: transparent !important; - } - } - BaseTabPanel::first-child { - flex-direction: column; - padding: 18px 24px; } ErrorMessage { position: sticky; @@ -110,50 +91,54 @@ export const ConnectionFormDialog = observer( }: ConnectionFormDialogProps) { const translate = useTranslate(); const controller = useController(ConnectionFormDialogController, driver, onClose); - const tab = useTabState({ - selectedId: 'options', - }); + const [loadProperties, setLoadProperties] = useState(false); return styled(useStyles(styles))( - - {translate('customConnection_options')} - {translate('customConnection_properties')} - - )} - footer={( - - )} - onReject={onClose} - > - - - - - - + + + + + setLoadProperties(true)} > + + + + )} + footer={( + - - - {controller.error.responseMessage && ( - - )} - + )} + onReject={onClose} + > + + + + + + + + + {controller.error.responseMessage && ( + + )} + + ); } ); diff --git a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx index b759af3c49..db45e66712 100644 --- a/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx +++ b/webapp/packages/custom-connection-plugin/src/CustomConnection/ConnectionFormDialog/DriverProperties/DriverProperties.tsx @@ -22,6 +22,7 @@ const styles = css` display: flex; flex: 1; flex-direction: column; + overflow: auto; } `; @@ -32,21 +33,21 @@ type DriverPropertyState = { type DriverPropertiesProps = { driver: DBDriver; state: DriverPropertyState; - isSelected: boolean; + loadProperties: boolean; } export const DriverProperties = observer(function DriverProperties({ driver, state, - isSelected, + loadProperties, }: DriverPropertiesProps) { const controller = useController(DriverPropertiesController, driver); useEffect(() => { - if (isSelected) { + if (loadProperties) { controller.loadDriverProperties(); } - }, [isSelected]); + }, [loadProperties]); return styled(useStyles(styles))(