From 913116b0fca89f187a771d30db688fd1d372f526 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Dec 2023 00:58:04 +0100 Subject: [PATCH 1/3] Restyle Recent activity registration and environment type selection --- src/components/Assets/FilterMenu/styles.ts | 2 +- .../EnvironmentTypeCard/StatusChip/styles.ts | 4 +- .../EnvironmentTypes/InsightCard/styles.ts | 6 +- .../pages/EnvironmentTypes/styles.ts | 8 +- .../EnvironmentTypePanel/index.tsx | 90 +++----- .../EnvironmentTypePanel/styles.ts | 127 ++++++----- .../EnvironmentTypePanel/types.ts | 9 + .../RecentActivity/LiveView/styles.ts | 11 +- .../ObservabilityStatusBadge/styles.ts | 4 +- .../RecentActivityTable/styles.ts | 7 +- .../TextField/TextField.stories.tsx | 30 +++ .../RegistrationPanel/TextField/index.tsx | 59 ++++++ .../RegistrationPanel/TextField/styles.ts | 134 ++++++++++++ .../RegistrationPanel/TextField/types.ts | 25 +++ .../RegistrationPanel/index.tsx | 159 ++++++++------ .../RegistrationPanel/styles.ts | 64 ++++-- .../RecentActivity/RegistrationPanel/types.ts | 4 +- src/components/RecentActivity/index.tsx | 7 +- src/components/RecentActivity/styles.ts | 37 +++- src/components/common/App/getTheme.ts | 198 ++++++++++++++++++ src/components/common/App/index.tsx | 5 +- src/components/common/App/styles.ts | 3 +- .../common/NewButton/NewButton.stories.tsx | 38 ++++ src/components/common/NewButton/index.tsx | 32 +++ src/components/common/NewButton/styles.ts | 74 +++++++ src/components/common/NewButton/types.ts | 29 +++ src/components/common/Tooltip/styles.ts | 2 +- src/components/common/icons/EnvelopeIcon.tsx | 34 +++ src/styled.d.ts | 36 ++++ 29 files changed, 1003 insertions(+), 235 deletions(-) create mode 100644 src/components/RecentActivity/RegistrationPanel/TextField/TextField.stories.tsx create mode 100644 src/components/RecentActivity/RegistrationPanel/TextField/index.tsx create mode 100644 src/components/RecentActivity/RegistrationPanel/TextField/styles.ts create mode 100644 src/components/RecentActivity/RegistrationPanel/TextField/types.ts create mode 100644 src/components/common/App/getTheme.ts create mode 100644 src/components/common/NewButton/NewButton.stories.tsx create mode 100644 src/components/common/NewButton/index.tsx create mode 100644 src/components/common/NewButton/styles.ts create mode 100644 src/components/common/NewButton/types.ts create mode 100644 src/components/common/icons/EnvelopeIcon.tsx diff --git a/src/components/Assets/FilterMenu/styles.ts b/src/components/Assets/FilterMenu/styles.ts index e7200da55..c6d0b0a6f 100644 --- a/src/components/Assets/FilterMenu/styles.ts +++ b/src/components/Assets/FilterMenu/styles.ts @@ -167,7 +167,7 @@ export const Tag = styled.div` border-radius: 2px; align-items: center; gap: 4px; - background: rgba(53 56 205 / 50%); + background: rgb(53 56 205 / 50%); font-size: 14px; padding: 4px; color: #dfe1e5; diff --git a/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/StatusChip/styles.ts b/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/StatusChip/styles.ts index d9e161686..ee74969e1 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/StatusChip/styles.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/StatusChip/styles.ts @@ -21,10 +21,10 @@ export const Container = styled.div` } switch (theme.mode) { case "light": - return "rgba(103 210 139 / 64%)"; + return "rgb(103 210 139 / 64%)"; case "dark": case "dark-jetbrains": - return "rgba(103 210 139 / 24%)"; + return "rgb(103 210 139 / 24%)"; } }}; `; diff --git a/src/components/Documentation/pages/EnvironmentTypes/InsightCard/styles.ts b/src/components/Documentation/pages/EnvironmentTypes/InsightCard/styles.ts index 061ef5b0c..27a81cd24 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/InsightCard/styles.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/InsightCard/styles.ts @@ -12,7 +12,7 @@ export const Container = styled.div` font-weight: 500; border-radius: 2px; border: 1px solid #434343; - box-shadow: 0 4px 8px 0 rgba(0 0 0 / 12%); + box-shadow: 0 4px 8px 0 rgb(0 0 0 / 12%); color: ${({ theme }) => { switch (theme.mode) { case "light": @@ -81,10 +81,10 @@ export const CountChip = styled(Chip)` background: ${({ theme, $count }) => { switch (theme.mode) { case "light": - return `rgba(${$count > 0 ? "29 198 147" : "224 0 54"} / 30%)`; + return `rgb(${$count > 0 ? "29 198 147" : "224 0 54"} / 30%)`; case "dark": case "dark-jetbrains": - return `rgba(${$count > 0 ? "103 210 139" : "249 57 103"} / 30%)`; + return `rgb(${$count > 0 ? "103 210 139" : "249 57 103"} / 30%)`; } }}; `; diff --git a/src/components/Documentation/pages/EnvironmentTypes/styles.ts b/src/components/Documentation/pages/EnvironmentTypes/styles.ts index 323c31496..6353def96 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/styles.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/styles.ts @@ -15,10 +15,10 @@ export const Container = styled.div` background: ${({ theme }) => { switch (theme.mode) { case "light": - return "rgba(235 236 240 / 59%)"; + return "rgb(235 236 240 / 59%)"; case "dark": case "dark-jetbrains": - return "rgba(66 65 65 / 59%)"; + return "rgb(66 65 65 / 59%)"; } }}; backdrop-filter: blur(12px); @@ -36,7 +36,7 @@ export const TopGradientBackground = styled.div` background: radial-gradient( 50% 50% at 50% 50%, #4f5da3 0%, - rgba(79 93 163 / 0%) 100% + rgb(79 93 163 / 0%) 100% ); filter: blur(5px); z-index: -1; @@ -52,7 +52,7 @@ export const BottomGradientBackground = styled.div` background: radial-gradient( 50% 50% at 50% 50%, #4f5da3 0%, - rgba(79 93 163 / 0%) 100% + rgb(79 93 163 / 0%) 100% ); filter: blur(5px); z-index: -1; diff --git a/src/components/RecentActivity/EnvironmentTypePanel/index.tsx b/src/components/RecentActivity/EnvironmentTypePanel/index.tsx index 59b1e7fd5..860ade15e 100644 --- a/src/components/RecentActivity/EnvironmentTypePanel/index.tsx +++ b/src/components/RecentActivity/EnvironmentTypePanel/index.tsx @@ -1,28 +1,13 @@ -import { ReactNode, useCallback, useState } from "react"; import { sendTrackingEvent } from "../../../utils/sendTrackingEvent"; -import { CodeDisplayIcon } from "../../common/icons/CodeDisplayIcon"; -import { InfiniteLoopIcon } from "../../common/icons/InfiniteLoopIcon"; +import { NewButton } from "../../common/NewButton"; +import { CodeIcon } from "../../common/icons/CodeIcon"; +import { InfinityIcon } from "../../common/icons/InfinityIcon"; import { trackingEvents } from "../tracking"; import { EnvironmentType } from "../types"; import * as s from "./styles"; -import { EnvironmentTypePanelProps } from "./types"; +import { EnvironmentTypeData, EnvironmentTypePanelProps } from "./types"; export const EnvironmentTypePanel = (props: EnvironmentTypePanelProps) => { - const [selectedType, setSelectedType] = useState(); - - const handleMouseEnter = useCallback( - (type: EnvironmentType) => setSelectedType(type), - [] - ); - - const handleMouseLeave = useCallback(() => setSelectedType(undefined), []); - - const handleFocus = useCallback( - (type: EnvironmentType) => setSelectedType(type), - [] - ); - const handleBlur = useCallback(() => setSelectedType(undefined), []); - const handleEnvironmentTypeButtonClick = (type: EnvironmentType) => { const typeData = environmentTypes.find((x) => x.type === type); @@ -35,45 +20,39 @@ export const EnvironmentTypePanel = (props: EnvironmentTypePanelProps) => { props.onEnvironmentTypeSelect(props.environment.originalName, type); }; - const environmentTypes: { - type: EnvironmentType; - title: string; - description: ReactNode; - icon: ReactNode; - }[] = [ + const environmentTypes: EnvironmentTypeData[] = [ { type: "local", title: "Local environment", description: "Define an environment for specific branches, types of tests or other criteria", - icon: + icon: , + button: ( + handleEnvironmentTypeButtonClick("local")} + label={"Add"} + buttonType={"primary"} + size={"large"} + /> + ) }, { type: "shared", title: "CI/Prod environment", description: "Connect to centralized org systems such as CI builds, production servers etc.", - icon: + icon: , + button: ( + handleEnvironmentTypeButtonClick("local")} + label={"Learn more"} + buttonType={"secondary"} + size={"large"} + /> + ) } ]; - const getEnvironmentTypeDescription = (type: EnvironmentType) => { - const data = environmentTypes.find((x) => x.type === type); - - if (!data) { - return null; - } - - return ( - <> - - {data.title} - - {data.description} - - ); - }; - return ( Choose environment type @@ -81,27 +60,18 @@ export const EnvironmentTypePanel = (props: EnvironmentTypePanelProps) => { Choose which environment type you would like to create - - {selectedType === "local" && getEnvironmentTypeDescription("local")} - {environmentTypes.map((x) => ( - handleEnvironmentTypeButtonClick(x.type)} - onMouseEnter={() => handleMouseEnter(x.type)} - onMouseLeave={() => handleMouseLeave()} - onFocus={() => handleFocus(x.type)} - onBlur={() => handleBlur()} - > + {x.icon} - {x.title} - + + {x.title} + {x.description} + + {x.button} + ))} - - {selectedType === "shared" && getEnvironmentTypeDescription("shared")} - ); diff --git a/src/components/RecentActivity/EnvironmentTypePanel/styles.ts b/src/components/RecentActivity/EnvironmentTypePanel/styles.ts index 2608e5a5d..e9eb79f66 100644 --- a/src/components/RecentActivity/EnvironmentTypePanel/styles.ts +++ b/src/components/RecentActivity/EnvironmentTypePanel/styles.ts @@ -1,9 +1,10 @@ import styled from "styled-components"; +import { grayScale } from "../../common/App/getTheme"; export const Container = styled.div` margin-top: 12px; display: flex; - gap: 8px; + gap: 4px; flex-direction: column; align-items: center; font-size: 14px; @@ -11,15 +12,15 @@ export const Container = styled.div` export const Title = styled.span` font-size: 16px; - font-weight: 600; + font-weight: 500; text-transform: capitalize; color: ${({ theme }) => { switch (theme.mode) { case "light": - return "#494b57"; + return grayScale[900]; case "dark": case "dark-jetbrains": - return "#dfe1e5"; + return grayScale[50]; } }}; `; @@ -28,95 +29,109 @@ export const Subtitle = styled.span` color: ${({ theme }) => { switch (theme.mode) { case "light": - return "#818594"; + return grayScale[600]; case "dark": case "dark-jetbrains": - return "#b4b8bf"; + return grayScale[400]; } }}; `; export const ContentContainer = styled.div` display: flex; - margin-top: 4px; + margin-top: 8px; gap: 16px; - width: 100%; `; -export const EnvironmentTypeDescription = styled.span` +export const EnvironmentTypeCard = styled.div` + border: 1px solid + ${({ theme }) => { + switch (theme.mode) { + case "light": + return grayScale[200]; + case "dark": + case "dark-jetbrains": + return grayScale[900]; + } + }}; + background: ${({ theme }) => { + switch (theme.mode) { + case "light": + return grayScale[50]; + case "dark": + case "dark-jetbrains": + return grayScale[1000]; + } + }}; + box-shadow: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "0 1px 5px 0 rgba(0 0 0 / 12%)"; + case "dark": + case "dark-jetbrains": + return "0 1px 4px 0 rgba(0 0 0 / 45%)"; + } + }}; + border-radius: 7px; display: flex; + width: 218px; + padding: 12px; flex-direction: column; - gap: 4px; - padding: 8px; - flex: 1; - - &:first-child { - text-align: right; - align-items: flex-end; - } -`; - -export const EnvironmentTypeDescriptionTitle = styled.span` - font-weight: 600; - text-transform: capitalize; + align-items: center; + gap: 8px; `; -export const EnvironmentTypeButton = styled.button` +export const EnvironmentTypeTextContainer = styled.div` display: flex; flex-direction: column; - padding: 12px; - gap: 8px; - text-transform: capitalize; align-items: center; - width: 120px; - border-radius: 4px; - font-weight: 600; - font-family: inherit; - font-size: 16px; - border: 1px solid transparent; + text-align: center; + color: ${grayScale[500]}; +`; + +export const EnvironmentTypeTitle = styled.span` color: ${({ theme }) => { switch (theme.mode) { case "light": - return "#494b57"; + return grayScale[900]; case "dark": case "dark-jetbrains": - return "#dfe1e5"; - } - }}; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#ebecf0"; - case "dark": - case "dark-jetbrains": - return "#393b40"; + return grayScale[100]; } }}; + font-weight: 500; +`; - &:hover, - &:focus { - border-color: ${({ theme }) => { +export const EnvironmentTypeIconContainer = styled.div` + display: flex; + border-radius: 4px; + padding: 6px; + border: 1px solid + ${({ theme }) => { switch (theme.mode) { case "light": - return "#92affa"; + return grayScale[300]; case "dark": case "dark-jetbrains": - return "#7c7c94"; + return grayScale[700]; } }}; - } -`; - -export const EnvironmentTypeIconContainer = styled.div` - border-radius: 4px; - padding: 14px 10px; background: ${({ theme }) => { switch (theme.mode) { case "light": - return "#dfe1e5"; + return grayScale[50]; + case "dark": + case "dark-jetbrains": + return grayScale[1000]; + } + }}; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return grayScale[800]; case "dark": case "dark-jetbrains": - return "#43454a"; + return grayScale[200]; } }}; `; diff --git a/src/components/RecentActivity/EnvironmentTypePanel/types.ts b/src/components/RecentActivity/EnvironmentTypePanel/types.ts index 03e47e380..6da7c876a 100644 --- a/src/components/RecentActivity/EnvironmentTypePanel/types.ts +++ b/src/components/RecentActivity/EnvironmentTypePanel/types.ts @@ -1,6 +1,15 @@ +import { ReactNode } from "react"; import { EnvironmentType, ExtendedEnvironment } from "../types"; export interface EnvironmentTypePanelProps { environment: ExtendedEnvironment; onEnvironmentTypeSelect: (environment: string, type: EnvironmentType) => void; } + +export interface EnvironmentTypeData { + type: EnvironmentType; + title: string; + description: ReactNode; + icon: ReactNode; + button: ReactNode; +} diff --git a/src/components/RecentActivity/LiveView/styles.ts b/src/components/RecentActivity/LiveView/styles.ts index 275d43b20..4e49dbae8 100644 --- a/src/components/RecentActivity/LiveView/styles.ts +++ b/src/components/RecentActivity/LiveView/styles.ts @@ -37,18 +37,9 @@ export const Container = styled.div` return "#d1d1d1"; case "dark": case "dark-jetbrains": - return "#323232s"; + return "#323232"; } }}; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#fbfdff"; - case "dark": - case "dark-jetbrains": - return "#383838"; - } - }}; `; export const Header = styled.div` diff --git a/src/components/RecentActivity/ObservabilityStatusBadge/styles.ts b/src/components/RecentActivity/ObservabilityStatusBadge/styles.ts index 0f94f5ef4..586992823 100644 --- a/src/components/RecentActivity/ObservabilityStatusBadge/styles.ts +++ b/src/components/RecentActivity/ObservabilityStatusBadge/styles.ts @@ -3,8 +3,8 @@ import { Link } from "../../common/Link"; export const Container = styled.div` border-radius: 8px; - border: 1px solid rgba(255 129 13 / 50%); - background: rgba(255 129 13 / 10%); + border: 1px solid rgb(255 129 13 / 50%); + background: rgb(255 129 13 / 10%); display: flex; justify-content: space-between; align-items: center; diff --git a/src/components/RecentActivity/RecentActivityTable/styles.ts b/src/components/RecentActivity/RecentActivityTable/styles.ts index b78f93ad5..16b4261a8 100644 --- a/src/components/RecentActivity/RecentActivityTable/styles.ts +++ b/src/components/RecentActivity/RecentActivityTable/styles.ts @@ -1,4 +1,5 @@ import styled from "styled-components"; +import { grayScale } from "../../common/App/getTheme"; import { getCodeFont } from "../../common/App/styles"; import { Link } from "../../common/Link"; @@ -43,7 +44,7 @@ export const TableHead = styled.thead<{ offset: number }>` case "dark": return "#0f0f0f"; case "dark-jetbrains": - return "#2b2d30"; + return grayScale[1200]; } }}; background: ${({ theme }) => { @@ -53,7 +54,7 @@ export const TableHead = styled.thead<{ offset: number }>` case "dark": return "#0f0f0f"; case "dark-jetbrains": - return "#2b2d30"; + return grayScale[1200]; } }}; `; @@ -91,7 +92,7 @@ export const TableBody = styled.tbody` case "dark": return "#0f0f0f"; case "dark-jetbrains": - return "#2b2d30"; + return grayScale[1200]; } }}; } diff --git a/src/components/RecentActivity/RegistrationPanel/TextField/TextField.stories.tsx b/src/components/RecentActivity/RegistrationPanel/TextField/TextField.stories.tsx new file mode 100644 index 000000000..8bfe1feca --- /dev/null +++ b/src/components/RecentActivity/RegistrationPanel/TextField/TextField.stories.tsx @@ -0,0 +1,30 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { TextField } from "."; +import { DigmaLogoIcon } from "../../../common/icons/DigmaLogoIcon"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Recent Activity/RegistrationPanel/TextField", + component: TextField, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + value: "some text" + } +}; + +export const WithIcon: Story = { + args: { + value: "some text", + icon: DigmaLogoIcon + } +}; diff --git a/src/components/RecentActivity/RegistrationPanel/TextField/index.tsx b/src/components/RecentActivity/RegistrationPanel/TextField/index.tsx new file mode 100644 index 000000000..021c331e6 --- /dev/null +++ b/src/components/RecentActivity/RegistrationPanel/TextField/index.tsx @@ -0,0 +1,59 @@ +import { FocusEvent, ForwardedRef, forwardRef, useState } from "react"; +import { CheckmarkCircleInvertedIcon } from "../../../common/icons/CheckmarkCircleInvertedIcon"; +import { CrossCircleIcon } from "../../../common/icons/CrossCircleIcon"; +import * as s from "./styles"; +import { TextFieldProps } from "./types"; + +export const TextFieldComponent = ( + props: TextFieldProps, + ref: ForwardedRef +) => { + const [isFocused, setIsFocused] = useState(false); + + const handleFocus = () => { + setIsFocused(true); + }; + + const handleBlur = (e: FocusEvent) => { + if (props.onBlur) { + props.onBlur(e); + } + setIsFocused(false); + }; + + return ( + + {props.icon && ( + + + + )} + + {props.isValid === true && ( + + + + )} + {props.isValid === false && ( + + + + )} + + ); +}; + +export const TextField = forwardRef(TextFieldComponent); diff --git a/src/components/RecentActivity/RegistrationPanel/TextField/styles.ts b/src/components/RecentActivity/RegistrationPanel/TextField/styles.ts new file mode 100644 index 000000000..042caf549 --- /dev/null +++ b/src/components/RecentActivity/RegistrationPanel/TextField/styles.ts @@ -0,0 +1,134 @@ +import styled from "styled-components"; +import { + grayScale, + greenScale, + primaryScale, + redScale +} from "../../../common/App/getTheme"; +import { ContainerProps, IconContainerProps, InputProps } from "./types"; + +export const Container = styled.div` + display: flex; + align-items: center; + gap: 4px; + padding: 6px 8px; + border-radius: 4px; + border: 1px solid + ${({ theme, $focused, $isValid }) => { + if ($isValid === false) { + switch (theme.mode) { + case "light": + return redScale[500]; + case "dark": + case "dark-jetbrains": + return redScale[300]; + } + } + + if ($focused) { + return primaryScale[300]; + } + + switch (theme.mode) { + case "light": + return grayScale[300]; + case "dark": + case "dark-jetbrains": + return grayScale[700]; + } + }}; + background: ${({ theme, $isValid }) => { + if ($isValid === false) { + switch (theme.mode) { + case "light": + return redScale[200]; + case "dark": + case "dark-jetbrains": + return redScale[400]; + } + } + + return "none"; + }}; +`; + +export const IconContainer = styled.div` + display: flex; + color: ${({ theme, $isValid }) => { + if ($isValid === false) { + switch (theme.mode) { + case "light": + return redScale[500]; + case "dark": + case "dark-jetbrains": + return redScale[300]; + } + } + + switch (theme.mode) { + case "light": + return grayScale[800]; + case "dark": + case "dark-jetbrains": + return grayScale[200]; + } + }}; +`; + +export const ValidationStatusIconContainer = styled(IconContainer)` + color: ${({ theme, $isValid }) => { + if ($isValid === true) { + return greenScale[300]; + } + + if ($isValid === false) { + switch (theme.mode) { + case "light": + return redScale[500]; + case "dark": + case "dark-jetbrains": + return redScale[300]; + } + } + }}; +`; + +export const Input = styled.input` + border: none; + background: none; + outline: none; + display: flex; + flex-grow: 1; + font-size: 14px; + color: ${({ theme, $isValid }) => { + if ($isValid === false) { + switch (theme.mode) { + case "light": + return redScale[500]; + case "dark": + case "dark-jetbrains": + return redScale[300]; + } + } + + switch (theme.mode) { + case "light": + return grayScale[900]; + case "dark": + case "dark-jetbrains": + return grayScale[100]; + } + }}; + + &::placeholder { + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return grayScale[400]; + case "dark": + case "dark-jetbrains": + return grayScale[600]; + } + }}; + } +`; diff --git a/src/components/RecentActivity/RegistrationPanel/TextField/types.ts b/src/components/RecentActivity/RegistrationPanel/TextField/types.ts new file mode 100644 index 000000000..ac5ecb103 --- /dev/null +++ b/src/components/RecentActivity/RegistrationPanel/TextField/types.ts @@ -0,0 +1,25 @@ +import { ChangeEventHandler, FocusEventHandler } from "react"; +import { IconProps } from "../../../common/icons/types"; + +export interface TextFieldProps { + placeholder?: string; + icon?: React.ComponentType; + value: string; + onChange: ChangeEventHandler; + className?: string; + isValid?: boolean; + onBlur?: FocusEventHandler; +} + +export interface ContainerProps { + $focused: boolean; + $isValid?: boolean; +} + +export interface IconContainerProps { + $isValid?: boolean; +} + +export interface InputProps { + $isValid?: boolean; +} diff --git a/src/components/RecentActivity/RegistrationPanel/index.tsx b/src/components/RecentActivity/RegistrationPanel/index.tsx index 6fc55ca37..fd5a62e66 100644 --- a/src/components/RecentActivity/RegistrationPanel/index.tsx +++ b/src/components/RecentActivity/RegistrationPanel/index.tsx @@ -1,46 +1,59 @@ -import { ChangeEvent, KeyboardEvent, useEffect, useRef, useState } from "react"; +import { KeyboardEvent, useEffect } from "react"; +import { Controller, useForm } from "react-hook-form"; import { isValidEmailFormat } from "../../../utils/isValidEmailFormat"; import { NewCircleLoader } from "../../common/NewCircleLoader"; -import { TextField } from "../../common/TextField"; import { CrossIcon } from "../../common/icons/CrossIcon"; -import { WarningCircleLargeIcon } from "../../common/icons/WarningCircleLargeIcon"; +import { EnvelopeIcon } from "../../common/icons/EnvelopeIcon"; +import { UserIcon } from "../../common/icons/UserIcon"; +import { TextField } from "./TextField"; import { isWorkEmail } from "./isWorkEmail"; import * as s from "./styles"; -import { RegistrationFormData, RegistrationPanelProps } from "./types"; - -const validateTextFields = (data: RegistrationFormData): string | null => { - if (data.fullName.length === 0) { - return "Full name is required"; - } +import { RegistrationFormValues, RegistrationPanelProps } from "./types"; +const validateEmail = (email: string): string | boolean => { const emailMessage = "Please enter a valid work email address"; - if (!isValidEmailFormat(data.email)) { + if (email.length === 0) { return emailMessage; } - if (!isWorkEmail(data.email)) { + if (!isValidEmailFormat(email)) { return emailMessage; } - return null; + if (!isWorkEmail(email)) { + return emailMessage; + } + + return true; }; -export const RegistrationPanel = (props: RegistrationPanelProps) => { - const [fullName, setFullName] = useState(""); - const [email, setEmail] = useState(""); - const fullNameTextFieldRef = useRef(null); +const formDefaultValues: RegistrationFormValues = { + fullName: "", + email: "" +}; - const errorMessage = validateTextFields({ fullName, email }); +export const RegistrationPanel = (props: RegistrationPanelProps) => { + const { + handleSubmit, + control, + getValues, + formState: { errors, isValid, touchedFields }, + setFocus + } = useForm({ + mode: "onChange", + defaultValues: formDefaultValues + }); + const values = getValues(); useEffect(() => { - fullNameTextFieldRef.current?.focus(); - }, []); + setFocus("fullName"); + }, [setFocus]); - const handleSubmitButtonClick = () => { + const onSubmit = (data: RegistrationFormValues) => { props.onSubmit({ - fullName: fullName.trim(), - email + fullName: data.fullName.trim(), + email: data.email }); }; @@ -48,23 +61,15 @@ export const RegistrationPanel = (props: RegistrationPanelProps) => { props.onClose(); }; - const handleFullNameTextFieldChange = (e: ChangeEvent) => { - setFullName(e.target.value); - }; - - const handleEmailTextFieldChange = (e: ChangeEvent) => { - setEmail(e.target.value); - }; - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === "Enter" && !errorMessage) { - props.onSubmit({ - fullName, - email - }); + if (e.key === "Enter" && isValid) { + onSubmit(values); } }; + const errorMessage = + Object.values(errors).length > 0 ? Object.values(errors)[0].message : ""; + return ( @@ -74,35 +79,63 @@ export const RegistrationPanel = (props: RegistrationPanelProps) => { Please register first to create new environments in Digma - - - {props.isRegistrationInProgress ? ( - - - - ) : ( + { + e.preventDefault(); + void handleSubmit(onSubmit)(e); + }} + > + ( + + )} + /> + ( + + )} + /> + + {errorMessage} + + {props.isRegistrationInProgress && ( + + + + )} - Submit - - )} - {email.length > 0 && errorMessage && ( - - - {errorMessage} - - )} + disabled={!isValid || props.isRegistrationInProgress} + label={"Submit"} + size={"large"} + type={"submit"} + form={"registrationForm"} + /> + ); }; diff --git a/src/components/RecentActivity/RegistrationPanel/styles.ts b/src/components/RecentActivity/RegistrationPanel/styles.ts index 751e29960..f6b55bff5 100644 --- a/src/components/RecentActivity/RegistrationPanel/styles.ts +++ b/src/components/RecentActivity/RegistrationPanel/styles.ts @@ -1,31 +1,43 @@ import styled from "styled-components"; -import { Button } from "../../common/Button"; +import { grayScale, redScale } from "../../common/App/getTheme"; +import { NewButton } from "../../common/NewButton"; export const Container = styled.div` display: flex; flex-direction: column; - gap: 8px; + gap: 4px; padding: 12px; - font-size: 14px; - border-radius: 4px; + font-size: 13px; + border-radius: 7px; width: 390px; height: fit-content; - color: ${({ theme }) => { + color: ${grayScale[500]}; + border: 1px solid + ${({ theme }) => { + switch (theme.mode) { + case "light": + return grayScale[200]; + case "dark": + case "dark-jetbrains": + return grayScale[900]; + } + }}; + box-shadow: ${({ theme }) => { switch (theme.mode) { case "light": - return "#818594"; + return "0 1px 5px 0 rgba(0 0 0 / 12%)"; case "dark": case "dark-jetbrains": - return "#b4b8bf"; + return "0 1px 4px 0 rgba(0 0 0 / 45%)"; } }}; background: ${({ theme }) => { switch (theme.mode) { case "light": - return "#ebecf0"; + return grayScale[50]; case "dark": case "dark-jetbrains": - return "#393b40"; + return grayScale[1000]; } }}; `; @@ -37,23 +49,28 @@ export const Header = styled.div` `; export const Title = styled.span` - font-weight: 600; font-size: 16px; text-transform: capitalize; color: ${({ theme }) => { switch (theme.mode) { case "light": - return "#494b57"; + return grayScale[900]; case "dark": case "dark-jetbrains": - return "#dfe1e5"; + return grayScale[100]; } }}; `; +export const Form = styled.form` + display: flex; + flex-direction: column; + gap: 8px; +`; + export const ButtonsContainer = styled.div` display: flex; - padding-top: 8px; + padding-top: 4px; gap: 8px; justify-content: flex-end; `; @@ -64,30 +81,39 @@ export const CloseButton = styled.button` background: none; border: none; cursor: pointer; + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return grayScale[800]; + case "dark": + case "dark-jetbrains": + return grayScale[200]; + } + }}; `; export const CircleLoaderContainer = styled.div` display: flex; justify-content: center; align-items: center; - flex-grow: 1; `; -export const SubmitButton = styled(Button)` - height: auto; +export const SubmitButton = styled(NewButton)` + align-self: flex-end; `; export const ErrorMessage = styled.span` display: flex; - gap: 4px; + font-size: 13px; + height: 15px; align-items: center; color: ${({ theme }) => { switch (theme.mode) { case "light": - return "#e00036"; + return redScale[500]; case "dark": case "dark-jetbrains": - return "#f93967"; + return redScale[300]; } }}; `; diff --git a/src/components/RecentActivity/RegistrationPanel/types.ts b/src/components/RecentActivity/RegistrationPanel/types.ts index cd31fdcf7..905c97d89 100644 --- a/src/components/RecentActivity/RegistrationPanel/types.ts +++ b/src/components/RecentActivity/RegistrationPanel/types.ts @@ -1,10 +1,10 @@ export interface RegistrationPanelProps { - onSubmit: (data: RegistrationFormData) => void; + onSubmit: (data: RegistrationFormValues) => void; onClose: () => void; isRegistrationInProgress: boolean; } -export interface RegistrationFormData { +export interface RegistrationFormValues { fullName: string; email: string; } diff --git a/src/components/RecentActivity/index.tsx b/src/components/RecentActivity/index.tsx index 23de9459d..6d251c26d 100644 --- a/src/components/RecentActivity/index.tsx +++ b/src/components/RecentActivity/index.tsx @@ -21,7 +21,7 @@ import { LiveData } from "./LiveView/types"; import { ObservabilityStatusBadge } from "./ObservabilityStatusBadge"; import { RecentActivityTable, isRecent } from "./RecentActivityTable"; import { RegistrationPanel } from "./RegistrationPanel"; -import { RegistrationFormData } from "./RegistrationPanel/types"; +import { RegistrationFormValues } from "./RegistrationPanel/types"; import { SetupOrgDigmaPanel } from "./SetupOrgDigmaPanel"; import { actions } from "./actions"; import * as s from "./styles"; @@ -311,7 +311,7 @@ export const RecentActivity = (props: RecentActivityProps) => { }); }; - const handleRegistrationSubmit = (formData: RegistrationFormData) => { + const handleRegistrationSubmit = (formData: RegistrationFormValues) => { window.sendMessageToDigma({ action: actions.REGISTER, payload: { @@ -392,6 +392,9 @@ export const RecentActivity = (props: RecentActivityProps) => { + {/* + + */} { + switch (mode) { + case "light": + return lightThemeColors; + case "dark": + case "dark-jetbrains": + return darkThemeColors; + } +}; + +export const getTheme = ( + mode: Mode, + mainFont: string, + codeFont: string +): DefaultTheme => { + return { + mode, + mainFont, + codeFont, + colors: getColors(mode) + }; +}; diff --git a/src/components/common/App/index.tsx b/src/components/common/App/index.tsx index 7d103c8b1..36a20cb88 100644 --- a/src/components/common/App/index.tsx +++ b/src/components/common/App/index.tsx @@ -8,6 +8,7 @@ import { isNull } from "../../../typeGuards/isNull"; import { isObject } from "../../../typeGuards/isObject"; import { isString } from "../../../typeGuards/isString"; import { ConfigContext } from "./ConfigContext"; +import { getTheme } from "./getTheme"; import { GlobalStyle } from "./styles"; import { AppProps, BackendInfo, DigmaStatus } from "./types"; @@ -264,10 +265,12 @@ export const App = (props: AppProps) => { }; }, []); + const theme = getTheme(mode, mainFont, codeFont); + return ( <> - + {props.children} diff --git a/src/components/common/App/styles.ts b/src/components/common/App/styles.ts index 16f1a4cda..eda5385b5 100644 --- a/src/components/common/App/styles.ts +++ b/src/components/common/App/styles.ts @@ -4,7 +4,8 @@ import { platform } from "../../../platform"; export const LAYERS = { MODAL: 1000, - TOOLTIP: 2000 + TOOLTIP: 2000, + OVERLAY: 3000 }; export const getMainFont = (customFont: string) => { diff --git a/src/components/common/NewButton/NewButton.stories.tsx b/src/components/common/NewButton/NewButton.stories.tsx new file mode 100644 index 000000000..50daf29ef --- /dev/null +++ b/src/components/common/NewButton/NewButton.stories.tsx @@ -0,0 +1,38 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { NewButton } from "."; +import { CrosshairIcon } from "../icons/CrosshairIcon"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "common/NewButton", + component: NewButton, + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: "fullscreen" + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + icon: CrosshairIcon + } +}; + +export const WithLabel: Story = { + args: { + label: "Click me", + icon: CrosshairIcon + } +}; + +export const Disabled: Story = { + args: { + label: "Click me", + icon: CrosshairIcon, + disabled: true + } +}; diff --git a/src/components/common/NewButton/index.tsx b/src/components/common/NewButton/index.tsx new file mode 100644 index 000000000..bca665bd6 --- /dev/null +++ b/src/components/common/NewButton/index.tsx @@ -0,0 +1,32 @@ +import * as s from "./styles"; +import { NewButtonProps } from "./types"; + +export const NewButton = (props: NewButtonProps) => { + const buttonType = props.buttonType || "primary"; + const buttonSize = props.size || "small"; + const iconSize = buttonSize === "large" ? 16 : 13; + + return ( + + {props.icon && } + {typeof props.label === "string" && ( + + {props.label} + + )} + + ); +}; diff --git a/src/components/common/NewButton/styles.ts b/src/components/common/NewButton/styles.ts new file mode 100644 index 000000000..b71ce3974 --- /dev/null +++ b/src/components/common/NewButton/styles.ts @@ -0,0 +1,74 @@ +import styled from "styled-components"; +import { DefaultTheme } from "styled-components/dist/types"; +import { ButtonElementProps, ButtonType, LabelProps } from "./types"; + +const getButtonStyles = ( + theme: DefaultTheme, + type: ButtonType, + state: "default" | "hover" | "hover" | "focus" | "disabled" +) => { + const backgroundColor = theme.colors.button[type].background; + const borderColor = theme.colors.button[type].border; + + return ` + color: ${theme.colors.button[type].icon[state]}; + background: ${backgroundColor ? backgroundColor[state] : "none"}; + border: ${borderColor ? `1px solid ${borderColor[state]}` : "none"}; + `; +}; + +export const Label = styled.span` + font-size: ${({ $size }) => { + if ($size === "large") { + return "16px"; + } + return "13px"; + }}; +`; + +export const Button = styled.button` + border-radius: 4px; + display: flex; + align-items: center; + gap: 4px; + margin: 0; + cursor: pointer; + width: fit-content; + padding: ${({ $size }) => { + if ($size === "large") { + return "4px 8px"; + } + return "4px"; + }}; + ${({ theme, $type }) => getButtonStyles(theme, $type, "default")} + + ${Label} { + color: ${({ theme, $type }) => theme.colors.button[$type].text.default}; + } + + &:hover { + ${({ theme, $type }) => getButtonStyles(theme, $type, "hover")} + + ${Label} { + color: ${({ theme, $type }) => theme.colors.button[$type].text.hover}; + } + } + + &:focus, + &:active { + ${({ theme, $type }) => getButtonStyles(theme, $type, "focus")} + + ${Label} { + color: ${({ theme, $type }) => theme.colors.button[$type].text.focus}; + } + } + + &:disabled { + cursor: initial; + ${({ theme, $type }) => getButtonStyles(theme, $type, "disabled")} + + ${Label} { + color: ${({ theme, $type }) => theme.colors.button[$type].text.disabled}; + } + } +`; diff --git a/src/components/common/NewButton/types.ts b/src/components/common/NewButton/types.ts new file mode 100644 index 000000000..b8b6c1655 --- /dev/null +++ b/src/components/common/NewButton/types.ts @@ -0,0 +1,29 @@ +import { ButtonHTMLAttributes } from "react"; +import { IconProps } from "../../common/icons/types"; + +export type ButtonType = "primary" | "secondary" | "tertiary"; +export type ButtonSize = "small" | "large"; + +export interface NewButtonProps { + icon?: React.ComponentType; + label?: string; + title?: string; + onClick?: () => void; + disabled?: boolean; + buttonType?: ButtonType; + className?: string; + size?: ButtonSize; + type?: ButtonHTMLAttributes["type"]; + form?: ButtonHTMLAttributes["form"]; +} + +export interface ButtonElementProps { + $type: ButtonType; + $size: ButtonSize; +} + +export interface LabelProps { + $type: ButtonType; + $size: ButtonSize; + $disabled?: boolean; +} diff --git a/src/components/common/Tooltip/styles.ts b/src/components/common/Tooltip/styles.ts index c526eae95..f22a4a93a 100644 --- a/src/components/common/Tooltip/styles.ts +++ b/src/components/common/Tooltip/styles.ts @@ -4,7 +4,7 @@ import { LAYERS } from "../App/styles"; export const TooltipContainer = styled.div` padding: 8px; border-radius: 4px; - box-shadow: 0 0 6px 0 rgba(0 0 0 / 15%); + box-shadow: 0 0 6px 0 rgb(0 0 0 / 15%); font-size: 14px; word-break: break-all; z-index: ${LAYERS.TOOLTIP}; diff --git a/src/components/common/icons/EnvelopeIcon.tsx b/src/components/common/icons/EnvelopeIcon.tsx new file mode 100644 index 000000000..e8ddd3c0e --- /dev/null +++ b/src/components/common/icons/EnvelopeIcon.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const EnvelopeIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + + ); +}; + +export const EnvelopeIcon = React.memo(EnvelopeIconComponent); diff --git a/src/styled.d.ts b/src/styled.d.ts index 08d7a70d1..bbb56a759 100644 --- a/src/styled.d.ts +++ b/src/styled.d.ts @@ -1,10 +1,46 @@ import "styled-components"; import { Mode } from "./globals"; +export interface ButtonThemeColors { + background?: { + default: string; + hover: string; + focus: string; + disabled: string; + }; + border?: { + default: string; + hover: string; + focus: string; + disabled: string; + }; + icon: { + default: string; + hover: string; + focus: string; + disabled: string; + }; + text: { + default: string; + hover: string; + focus: string; + disabled: string; + }; +} + +export interface ThemeColors { + button: { + primary: ButtonThemeColors; + secondary: ButtonThemeColors; + tertiary: ButtonThemeColors; + }; +} + declare module "styled-components" { export interface DefaultTheme { mode: Mode; mainFont: string; codeFont: string; + colors: ThemeColors; } } From 10a2dd028299ffbfff65c5eaaec788db86410b65 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Dec 2023 01:10:25 +0100 Subject: [PATCH 2/3] Update package.json --- package-lock.json | 22 ++++++++++++++++++++++ package.json | 1 + 2 files changed, 23 insertions(+) diff --git a/package-lock.json b/package-lock.json index 690f5fdac..99ca29309 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "react-cool-dimensions": "^3.0.1", "react-dom": "^18.2.0", "react-helmet": "^6.1.0", + "react-hook-form": "^7.48.2", "react-scrollbar-size": "^5.0.0", "react-syntax-highlighter": "^15.5.0", "react-transition-group": "^4.4.5", @@ -16003,6 +16004,21 @@ "react": ">=16.3.0" } }, + "node_modules/react-hook-form": { + "version": "7.48.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz", + "integrity": "sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-inspector": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", @@ -30484,6 +30500,12 @@ "react-side-effect": "^2.1.0" } }, + "react-hook-form": { + "version": "7.48.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz", + "integrity": "sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A==", + "requires": {} + }, "react-inspector": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", diff --git a/package.json b/package.json index 92a6b349f..223782271 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "react-cool-dimensions": "^3.0.1", "react-dom": "^18.2.0", "react-helmet": "^6.1.0", + "react-hook-form": "^7.48.2", "react-scrollbar-size": "^5.0.0", "react-syntax-highlighter": "^15.5.0", "react-transition-group": "^4.4.5", From 9264180aeea0064b3e5ea1fa179d5734f2c0f297 Mon Sep 17 00:00:00 2001 From: Kyrylo Shmidt Date: Tue, 5 Dec 2023 01:13:58 +0100 Subject: [PATCH 3/3] Fix styles --- src/components/RecentActivity/styles.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/RecentActivity/styles.ts b/src/components/RecentActivity/styles.ts index 2a9769863..b2811bd19 100644 --- a/src/components/RecentActivity/styles.ts +++ b/src/components/RecentActivity/styles.ts @@ -45,7 +45,6 @@ export const RecentActivityContainer = styled.div` height: 100%; overflow: auto; box-sizing: border-box; - /* position: relative; */ `; // export const RecentActivityContainerBackground = styled.div`