diff --git a/.storybook/helpers/styles.ts b/.storybook/helpers/styles.ts index 773cde5260..d66b8155a4 100644 --- a/.storybook/helpers/styles.ts +++ b/.storybook/helpers/styles.ts @@ -1,9 +1,10 @@ import styled from 'styled-components' import { Theme } from 'uiSrc/components/base/theme/types' +import PageBody from 'uiSrc/components/base/layout/page/PageBody' -export const StyledContainer = styled.div` - padding: 50px; +export const StyledContainer = styled(PageBody)` height: max-content; + max-height: 100%; overflow: hidden; overflow-y: auto; background-color: ${({ theme }: { theme: Theme }) => diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html index a3407dc6d5..240c7771a1 100644 --- a/.storybook/preview-head.html +++ b/.storybook/preview-head.html @@ -13,6 +13,13 @@ min-height: 100vh; } + #storybook-root { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + } + .sbdocs-wrapper div:has(>div>.toc-wrapper){ width:14rem; } diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index c21141eebb..1120831996 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -9,6 +9,7 @@ import { CommonStyles, themeDark, themeLight, themeOld } from '@redis-ui/styles' import 'modern-normalize/modern-normalize.css' import '@redis-ui/styles/normalized-styles.css' import '@redis-ui/styles/fonts.css' +import 'uiSrc/pages/home/styles.scss' import { RootStoryLayout } from './RootStoryLayout' import { StoryContextProvider } from './Story.context' import { useStoryContext } from 'storybook/internal/preview-api' @@ -18,12 +19,13 @@ import { type Theme } from 'uiSrc/components/base/theme/types' import { Provider } from 'react-redux' import { store } from 'uiSrc/slices/store' import Router from 'uiSrc/Router' +import { StyledContainer } from './helpers/styles' const parameters: Parameters = { parameters: { layout: 'centered', }, - actions: { argTypesRegex: '^on[A-Z].*' }, + actions: { argTypesRegex: '^on.*' }, controls: { disableSaveFromUI: true, matchers: { @@ -51,7 +53,7 @@ const parameters: Parameters = { const GlobalStoryStyles = createGlobalStyle` .sb-show-main, .docs-story { background: ${({ theme }: { theme: Theme }) => theme.globals.body.bgColor}; - color: ${({ theme }: { theme: Theme }) => theme.globals.body.textColor}; + color: ${({ theme }: { theme: Theme }) => theme.components.typography.colors.primary}; } ` @@ -65,7 +67,9 @@ const preview: Preview = { - + + + diff --git a/redisinsight/ui/src/components/auto-discover/Header.tsx b/redisinsight/ui/src/components/auto-discover/Header.tsx new file mode 100644 index 0000000000..13947aca79 --- /dev/null +++ b/redisinsight/ui/src/components/auto-discover/Header.tsx @@ -0,0 +1,61 @@ +import React, { ReactNode } from 'react' + +import { Col, FlexItem, Row } from 'uiSrc/components/base/layout/flex' +import { EmptyButton } from 'uiSrc/components/base/forms/buttons' +import { ChevronLeftIcon } from 'uiSrc/components/base/icons' +import { + PageSubTitle, + PageTitle, + SearchContainer, + SearchForm, +} from 'uiSrc/components/auto-discover/index' +import { SearchInput } from 'uiSrc/components/base/inputs' + +type HeaderProps = { + title: ReactNode + subTitle?: ReactNode + onBack: () => void + onQueryChange: (query: string) => void + backButtonText?: string +} +export const Header = ({ + title, + subTitle, + onBack, + onQueryChange, + backButtonText = 'Add databases', +}: HeaderProps) => { + return ( + + + + {backButtonText} + + + {title} + + {subTitle && ( + + {subTitle} + + )} + + + + + + + + + + ) +} diff --git a/redisinsight/ui/src/components/auto-discover/index.ts b/redisinsight/ui/src/components/auto-discover/index.ts index b5d9c263d2..3618342256 100644 --- a/redisinsight/ui/src/components/auto-discover/index.ts +++ b/redisinsight/ui/src/components/auto-discover/index.ts @@ -1,20 +1,20 @@ +import React from 'react' import styled from 'styled-components' import { Checkbox } from 'uiSrc/components/base/forms/checkbox/Checkbox' import { Text, Title } from 'uiSrc/components/base/text' import { Theme } from 'uiSrc/components/base/theme/types' -import { FlexItem } from 'uiSrc/components/base/layout/flex' +import { Col, FlexItem } from 'uiSrc/components/base/layout/flex' import { FormField } from 'uiSrc/components/base/forms/FormField' import { IconButton } from 'uiSrc/components/base/forms/buttons' import { CopyIcon } from 'uiSrc/components/base/icons' export const PageTitle = styled(Title).attrs({ - size: 'M', + size: 'L', })` padding-bottom: ${({ theme }: { theme: Theme }) => theme.core.space.space050}; ` export const PageSubTitle = styled(Text).attrs({ size: 'S', - color: 'subdued', component: 'span', })` padding-bottom: ${({ theme }: { theme: Theme }) => theme.core.space.space100}; @@ -26,12 +26,28 @@ export const SearchContainer = styled(FlexItem)` export const SearchForm = styled(FormField)` width: 266px; ` -export const Footer = styled(FlexItem).attrs({ - grow: false, -})` +export const Footer = styled(FlexItem).attrs<{ + grow?: boolean | number + padding?: React.ComponentProps['padding'] +}>(({ grow, padding }) => ({ + grow: grow ?? false, + padding: padding ?? 6, +}))` border-top: 1px solid ${({ theme }: { theme: Theme }) => theme.semantic.color.border.neutral400}; ` + +export const DatabaseContainer = styled(Col)` + position: relative; + padding: ${({ theme }: { theme: Theme }) => + `${theme.core.space.space250} ${theme.core.space.space200} 0 ${theme.core.space.space200}`}; + @media only screen and (min-width: 768px) { + padding: ${({ theme }: { theme: Theme }) => + `${theme.core.space.space400} ${theme.core.space.space200} 0 ${theme.core.space.space400}`}; + max-width: calc(100vw - 95px); + } +` + export const DatabaseWrapper = styled.div` height: auto; scrollbar-width: thin; @@ -42,10 +58,6 @@ export const DatabaseWrapper = styled.div` theme.semantic.color.background.neutral100}; flex-grow: 1; overflow: hidden; - - .column_status { - text-transform: capitalize; - } ` export const SelectAllCheckbox = styled(Checkbox)` & svg { @@ -53,23 +65,29 @@ export const SelectAllCheckbox = styled(Checkbox)` } ` export const CellText = styled(Text).attrs({ - size: 'S', + size: 'M', component: 'span', -})`` - -export const CopyPublicEndpointText = styled(CellText)` +})` max-width: 100%; display: inline-block; width: auto; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; +` + +export const CopyPublicEndpointText = styled(CellText)` vertical-align: top; ` + +export const StatusColumnText = styled(CellText)` + text-transform: capitalize; +` export const CopyBtn = styled(IconButton).attrs({ icon: CopyIcon, + size: 'L', })` - margin-left: 25px; + margin-left: 15px; opacity: 0; height: 0; transition: opacity 0.25s ease-in-out; @@ -83,8 +101,6 @@ export const CopyTextContainer = styled.div` padding-right: 34px; position: relative; * { - color: ${({ theme }: { theme: Theme }) => - theme.semantic.color.text.primary500}; } &:hover ${CopyBtn} { diff --git a/redisinsight/ui/src/components/base/forms/buttons/Button.tsx b/redisinsight/ui/src/components/base/forms/buttons/Button.tsx index c8a0592ce4..d1bde85893 100644 --- a/redisinsight/ui/src/components/base/forms/buttons/Button.tsx +++ b/redisinsight/ui/src/components/base/forms/buttons/Button.tsx @@ -2,6 +2,8 @@ import { Button } from '@redis-ui/components' import React from 'react' import { LoaderLargeIcon } from 'uiSrc/components/base/icons' import { BaseButtonProps } from 'uiSrc/components/base/forms/buttons/button.styles' +import { Spacer } from 'uiSrc/components/base/layout' +import styled from 'styled-components' type ButtonSize = 'small' | 'medium' | 'large' type SizeKey = 'small' | 's' | 'medium' | 'm' | 'large' | 'l' @@ -59,7 +61,11 @@ export const IconSizes = { medium: '20px', large: '24px', } - +const Wrapper = styled.div` + svg { + display: block; + } +` export const ButtonIcon = ({ buttonSide, icon, @@ -82,11 +88,12 @@ export const ButtonIcon = ({ if (size) { iconSize = IconSizes[size] } + const spacer = return ( - + + {buttonSide === 'right' && spacer} + + {buttonSide === 'left' && spacer} + ) } diff --git a/redisinsight/ui/src/components/base/icons/Icon.tsx b/redisinsight/ui/src/components/base/icons/Icon.tsx index b5016359d4..7ad6ddae16 100644 --- a/redisinsight/ui/src/components/base/icons/Icon.tsx +++ b/redisinsight/ui/src/components/base/icons/Icon.tsx @@ -3,11 +3,12 @@ import { useTheme } from '@redis-ui/styles' import cx from 'classnames' import { IconSizeType } from '@redis-ui/icons' import { MonochromeIconProps } from 'uiSrc/components/base/icons' +import { Theme } from 'uiSrc/components/base/theme/types' type BaseIconProps = Omit & { icon: React.ComponentType color?: - | keyof ReturnType['semantic']['color']['icon'] + | keyof Theme['semantic']['color']['icon'] | 'currentColor' | (string & {}) size?: IconSizeType | null @@ -30,7 +31,7 @@ const sizesMap = { * @returns A boolean indicating if the color is valid and a type predicate */ function isValidIconColor( - theme: ReturnType, + theme: Theme, color: string | number | symbol, ): color is keyof typeof theme.semantic.color.icon { return color in theme.semantic.color.icon diff --git a/redisinsight/ui/src/components/base/layout/flex/flex.styles.ts b/redisinsight/ui/src/components/base/layout/flex/flex.styles.ts index aca8a3d8d8..cc95955d17 100644 --- a/redisinsight/ui/src/components/base/layout/flex/flex.styles.ts +++ b/redisinsight/ui/src/components/base/layout/flex/flex.styles.ts @@ -222,46 +222,46 @@ export const flexItemStyles = { `, }, padding: { - '0': css` + 0: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space000}; `, - '1': css` + 1: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space010}; `, - '2': css` + 2: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space025}; `, - '3': css` + 3: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space050}; `, - '4': css` + 4: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space100}; `, - '5': css` + 5: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space150}; `, - '6': css` + 6: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space200}; `, - '7': css` + 7: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space250}; `, - '8': css` + 8: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space300}; `, - '9': css` + 9: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space400}; `, - '10': css` + 10: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space500}; `, - '11': css` + 11: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space550}; `, - '12': css` + 12: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space600}; `, - '13': css` + 13: css` padding: ${({ theme }: { theme: Theme }) => theme.core.space.space800}; `, }, @@ -285,33 +285,19 @@ export const VALID_GROW_VALUES = [ 10, ] as const -export const VALID_PADDING_VALUES = [ - null, - undefined, - true, - false, - 0, // '0', - 1, // '0.1rem: 1,25px', - 2, // '0.2rem: 2,5px', - 3, // '0.4rem: 5px', - 4, // '0.8rem: 10px', - 5, // '1.2rem: 15px', - 6, // '1.6rem: 20px', - 7, // '2rem: 25px', - 8, // '2.4rem: 30px', - 9, // '3.2rem: 40px', - 10, // '4rem: 50px', - 11, // '4.4rem: 55px', - 12, // '4.8rem: 60px', - 13, // '6.4rem: 80px', -] as const +export type PaddingType = + | keyof typeof flexItemStyles.padding + | null + | undefined + | true + | false export type FlexItemProps = React.HTMLAttributes & PropsWithChildren & CommonProps & { grow?: (typeof VALID_GROW_VALUES)[number] $direction?: (typeof dirValues)[number] - $padding?: (typeof VALID_PADDING_VALUES)[number] + $padding?: PaddingType $gap?: GapSizeType } @@ -339,10 +325,7 @@ export const StyledFlexItem = styled.div` if ($padding === true) { return flexItemStyles.padding['4'] // Default padding (space100) } - if ( - typeof $padding === 'number' && - flexItemStyles.padding[$padding] !== undefined - ) { + if (flexItemStyles.padding[$padding] !== undefined) { return flexItemStyles.padding[$padding] } return '' diff --git a/redisinsight/ui/src/components/base/layout/flex/flex.tsx b/redisinsight/ui/src/components/base/layout/flex/flex.tsx index 9083099a59..b0c7bdc2a2 100644 --- a/redisinsight/ui/src/components/base/layout/flex/flex.tsx +++ b/redisinsight/ui/src/components/base/layout/flex/flex.tsx @@ -5,10 +5,10 @@ import { FlexItemProps, FlexProps, GridProps, + PaddingType, StyledFlex, StyledFlexItem, StyledGrid, - VALID_PADDING_VALUES, } from 'uiSrc/components/base/layout/flex/flex.styles' export const Grid = ({ children, className, ...rest }: GridProps) => { @@ -77,7 +77,7 @@ export const FlexGroup = ({ * Column Component * * A Column component is a special type of FlexGroup that is meant to be used when you - * want to layout a group of items in a vertical column. It is functionally equivalent to + * want to lay out a group of items in a vertical column. It is functionally equivalent to * using a FlexGroup with a direction of 'column', but includes some additional conveniences. * * This is the preferred API of a component that is not meant to be distributed but widely used in our project @@ -154,7 +154,7 @@ export const FlexItem = ({ direction, ...rest }: Omit & { - padding?: (typeof VALID_PADDING_VALUES)[number] + padding?: PaddingType direction?: (typeof dirValues)[number] }) => { const classes = classNames('RI-flex-item', className) diff --git a/redisinsight/ui/src/components/base/layout/page/page.styles.ts b/redisinsight/ui/src/components/base/layout/page/page.styles.ts index 05218505d6..a2078712a7 100644 --- a/redisinsight/ui/src/components/base/layout/page/page.styles.ts +++ b/redisinsight/ui/src/components/base/layout/page/page.styles.ts @@ -2,6 +2,7 @@ import styled, { css } from 'styled-components' import { CSSProperties, HTMLAttributes } from 'react' import { StyledPageHeader } from 'uiSrc/components/base/layout/page/page-heading.styles' import { StyledPageBody } from 'uiSrc/components/base/layout/page/page-body.styles' +import { Theme } from 'uiSrc/components/base/theme/types' export const PageClassNames = { page: 'RI-page', @@ -123,7 +124,8 @@ export const StyledPage = styled.div< } >` display: flex; - background-color: var(--euiPageBackgroundColor); + background-color: ${({ theme }: { theme: Theme }) => + theme.semantic.color.background.neutral100}; /* Ensure Safari doesn't shrink height beyond contents */ flex-shrink: 0; /* Ensure Firefox doesn't expand width beyond bounds */ diff --git a/redisinsight/ui/src/components/base/layout/spacer/spacer.spec.tsx b/redisinsight/ui/src/components/base/layout/spacer/spacer.spec.tsx new file mode 100644 index 0000000000..00f700433b --- /dev/null +++ b/redisinsight/ui/src/components/base/layout/spacer/spacer.spec.tsx @@ -0,0 +1,51 @@ +import React from 'react' + +import { render } from 'uiSrc/utils/test-utils' +import { Spacer } from './spacer' +import { SpacerProps } from './spacer.styles' + +const sizeToValue = { + xs: '0.2rem', + s: '0.4rem', + m: '0.8rem', + l: '1.6rem', + xl: '2rem', + xxl: '2.4rem', +} as const + +describe('Spacer', () => { + test('is rendered', () => { + const { container } = render() + + expect(container.firstChild).toBeTruthy() + }) + + describe('Size', () => { + Object.entries(sizeToValue).forEach(([size, value]) => { + it(`size '${size}' is rendered`, () => { + const { container } = render( + , + ) + expect(container.firstChild).toHaveStyle(`height: ${value}`) + }) + }) + }) + + describe('Direction', () => { + it(`width is rendered for Horizontal direction`, () => { + const { container } = render() + expect(container.firstChild).toHaveStyle(`height: 1.6rem`) + expect(container.firstChild).not.toHaveStyle(`width: 1.6rem`) + }) + it(`width is rendered for explicit Horizontal direction`, () => { + const { container } = render() + expect(container.firstChild).toHaveStyle(`width: 1.6rem`) + expect(container.firstChild).not.toHaveStyle(`height: 1.6rem`) + }) + it(`height is rendered for Vertical direction`, () => { + const { container } = render() + expect(container.firstChild).toHaveStyle('height: 1.6rem') + expect(container.firstChild).not.toHaveStyle(`width: 1.6rem`) + }) + }) +}) diff --git a/redisinsight/ui/src/components/base/layout/spacer/spacer.styles.ts b/redisinsight/ui/src/components/base/layout/spacer/spacer.styles.ts index 317aed0aeb..995d5ac8e8 100644 --- a/redisinsight/ui/src/components/base/layout/spacer/spacer.styles.ts +++ b/redisinsight/ui/src/components/base/layout/spacer/spacer.styles.ts @@ -14,6 +14,7 @@ export type SpacerProps = CommonProps & HTMLAttributes & { children?: ReactNode size?: SpacerSize | ThemeSpacingKey + direction?: 'horizontal' | 'vertical' } export const spacerStyles: Record> = { @@ -54,7 +55,19 @@ const getSpacingValue = ( return spacerStyles[size as SpacerSize] } -export const StyledSpacer = styled.div` +type StyledSpacerType = Omit & { + $direction: SpacerProps['direction'] +} + +export const StyledSpacer = styled.div` flex-shrink: 0; - height: ${({ size = 'l', theme }) => getSpacingValue(size, theme)}; + ${({ $direction = 'vertical', size = 'l', theme }) => { + return $direction === 'horizontal' + ? css` + width: ${getSpacingValue(size, theme)}; + ` + : css` + height: ${getSpacingValue(size, theme)}; + ` + }} ` diff --git a/redisinsight/ui/src/components/base/layout/spacer/spacer.tsx b/redisinsight/ui/src/components/base/layout/spacer/spacer.tsx index fd3c0491d9..7274eac814 100644 --- a/redisinsight/ui/src/components/base/layout/spacer/spacer.tsx +++ b/redisinsight/ui/src/components/base/layout/spacer/spacer.tsx @@ -16,9 +16,18 @@ import { SpacerProps, StyledSpacer } from './spacer.styles' * * The default value for `size` is 'l'. */ -export const Spacer = ({ className, children, ...rest }: SpacerProps) => { +export const Spacer = ({ + className, + children, + direction, + ...rest +}: SpacerProps) => { return ( - + {children} ) diff --git a/redisinsight/ui/src/components/base/link/UserProfileLink.tsx b/redisinsight/ui/src/components/base/link/UserProfileLink.tsx index 970caa559e..b854d03002 100644 --- a/redisinsight/ui/src/components/base/link/UserProfileLink.tsx +++ b/redisinsight/ui/src/components/base/link/UserProfileLink.tsx @@ -1,11 +1,11 @@ -import styled from "styled-components" -import { useTheme } from "@redis-ui/styles" -import { Link } from "./Link" +import styled from 'styled-components' +import { Link } from './Link' +import { Theme } from 'uiSrc/components/base/theme/types' export const UserProfileLink = styled(Link)` padding: 8px 12px !important; width: 100%; - color: ${({ theme }: { theme: ReturnType }) => + color: ${({ theme }: { theme: Theme }) => theme.semantic.color.text.informative400} !important; text-decoration: none !important; diff --git a/redisinsight/ui/src/components/input-field-sentinel/InputFieldSentinel.tsx b/redisinsight/ui/src/components/input-field-sentinel/InputFieldSentinel.tsx index 26fb9aba9a..8b14ac7fad 100644 --- a/redisinsight/ui/src/components/input-field-sentinel/InputFieldSentinel.tsx +++ b/redisinsight/ui/src/components/input-field-sentinel/InputFieldSentinel.tsx @@ -1,6 +1,7 @@ -import { omit } from 'lodash' import React, { useState } from 'react' -import cx from 'classnames' +import styled from 'styled-components' +import { omit } from 'lodash' + import { useDebouncedEffect } from 'uiSrc/services' import { NumericInput, @@ -9,7 +10,6 @@ import { } from 'uiSrc/components/base/inputs' import { RiIcon } from 'uiSrc/components/base/icons/RiIcon' -import styles from './styles.module.scss' export enum SentinelInputFieldType { Text = 'text', @@ -33,6 +33,14 @@ export interface Props { append?: React.ReactElement onChangedInput: (name: string, value: string) => void } +const InputInvalidIcon = styled(RiIcon).attrs({ + color: 'danger500', + type: 'ToastDangerIcon', +})` + position: absolute; + top: calc(50% - 9px); + right: 10px; +` const InputFieldSentinel = (props: Props) => { const { @@ -82,13 +90,7 @@ const InputFieldSentinel = (props: Props) => { data-testid="sentinel-input-number" /> )} - {isInvalid && ( - - )} + {isInvalid && } ) } diff --git a/redisinsight/ui/src/components/message-bar/MessageBar.tsx b/redisinsight/ui/src/components/message-bar/MessageBar.tsx index 3ac33e041c..4244eb4d9c 100644 --- a/redisinsight/ui/src/components/message-bar/MessageBar.tsx +++ b/redisinsight/ui/src/components/message-bar/MessageBar.tsx @@ -23,9 +23,7 @@ const MessageBar = ({ children, opened }: Props) => { return ( - - {children} - + {children} {
- - <b data-testid="page-header-title">{title}</b> - + {title && ( + + <ColorText variant="semiBold" data-testid="page-header-title"> + {title} + </ColorText> + + )} {subtitle ? {subtitle} : ''}
{children ? <>{children} : ''} diff --git a/redisinsight/ui/src/components/triggers/insights-trigger/InsightsTrigger.tsx b/redisinsight/ui/src/components/triggers/insights-trigger/InsightsTrigger.tsx index 32acdc861a..2530803b04 100644 --- a/redisinsight/ui/src/components/triggers/insights-trigger/InsightsTrigger.tsx +++ b/redisinsight/ui/src/components/triggers/insights-trigger/InsightsTrigger.tsx @@ -98,6 +98,7 @@ const InsightsTrigger = (props: Props) => { onClick={handleClickTrigger} data-testid="insights-trigger" isOpen={isInsightsOpen} + aria-label="Insights-trigger" /> {isHighlighted && instanceId && } diff --git a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.tsx b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.tsx index dab70b8298..911d6c2cca 100644 --- a/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.tsx +++ b/redisinsight/ui/src/pages/autodiscover-cloud/redis-cloud-databases-result/RedisCloudDatabasesResult.tsx @@ -118,7 +118,7 @@ const RedisCloudDatabaseListResult = ({ columns, onBack, onView }: Props) => {
-
+
{message}}
-