diff --git a/src/components/Insights/common/InsightCard/IconButton/IconButton.stories.tsx b/src/components/Insights/common/InsightCard/IconButton/IconButton.stories.tsx new file mode 100644 index 000000000..c6216d8bf --- /dev/null +++ b/src/components/Insights/common/InsightCard/IconButton/IconButton.stories.tsx @@ -0,0 +1,34 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { IconButton } from "."; +import { JiraLogoIcon } from "../../../../common/icons/16px/JiraLogoIcon"; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Insights/common/InsightCard/IconButton", + component: IconButton, + 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: { + component: JiraLogoIcon + } + } +}; + +export const Disabled: Story = { + args: { + icon: { + component: JiraLogoIcon + }, + isDisabled: true + } +}; diff --git a/src/components/Insights/common/InsightCard/IconButton/index.tsx b/src/components/Insights/common/InsightCard/IconButton/index.tsx new file mode 100644 index 000000000..201d482ce --- /dev/null +++ b/src/components/Insights/common/InsightCard/IconButton/index.tsx @@ -0,0 +1,22 @@ +import { ForwardedRef, forwardRef } from "react"; +import * as s from "./styles"; +import { IconButtonProps } from "./types"; + +export const IconButtonComponent = ( + props: IconButtonProps, + ref: ForwardedRef +) => ( + + + +); + +export const IconButton = forwardRef(IconButtonComponent); diff --git a/src/components/Insights/common/InsightCard/IconButton/styles.ts b/src/components/Insights/common/InsightCard/IconButton/styles.ts new file mode 100644 index 000000000..5a5816be7 --- /dev/null +++ b/src/components/Insights/common/InsightCard/IconButton/styles.ts @@ -0,0 +1,22 @@ +import styled from "styled-components"; + +export const Button = styled.button` + display: flex; + border-radius: 2px; + margin: 0; + cursor: pointer; + padding: 4px; + border: none; + background: none; + color: ${({ theme }) => theme.colors.v3.icon.disabled}; + + &:disabled { + cursor: initial; + } + + &:hover:enabled, + &:focus:enabled, + &:active:enabled { + background: ${({ theme }) => theme.colors.v3.surface.highlight}; + } +`; diff --git a/src/components/Insights/common/InsightCard/IconButton/types.ts b/src/components/Insights/common/InsightCard/IconButton/types.ts new file mode 100644 index 000000000..57a692f7a --- /dev/null +++ b/src/components/Insights/common/InsightCard/IconButton/types.ts @@ -0,0 +1,14 @@ +import { ComponentType } from "react"; +import { IconProps } from "../../../../common/icons/types"; + +export interface IconButtonProps { + icon: { + component: ComponentType; + color?: string; + size?: number; + }; + title?: string; + onClick?: () => void; + isDisabled?: boolean; + className?: string; +} diff --git a/src/components/Navigation/CodeButton/AnimatedCodeButton/index.tsx b/src/components/Navigation/CodeButton/AnimatedCodeButton/index.tsx index f23174dde..de4432387 100644 --- a/src/components/Navigation/CodeButton/AnimatedCodeButton/index.tsx +++ b/src/components/Navigation/CodeButton/AnimatedCodeButton/index.tsx @@ -1,12 +1,8 @@ -import { ForwardedRef, forwardRef } from "react"; import * as s from "./styles"; import { AnimatedCodeButtonProps } from "./types"; -export const AnimatedCodeButtonComponent = ( - props: AnimatedCodeButtonProps, - ref: ForwardedRef -) => ( - +export const AnimatedCodeButton = (props: AnimatedCodeButtonProps) => ( + @@ -14,5 +10,3 @@ export const AnimatedCodeButtonComponent = ( ); - -export const AnimatedCodeButton = forwardRef(AnimatedCodeButtonComponent); diff --git a/src/components/Navigation/CodeButton/AnimatedCodeButton/types.ts b/src/components/Navigation/CodeButton/AnimatedCodeButton/types.ts index b15edf78e..2149393bb 100644 --- a/src/components/Navigation/CodeButton/AnimatedCodeButton/types.ts +++ b/src/components/Navigation/CodeButton/AnimatedCodeButton/types.ts @@ -1,3 +1,3 @@ export interface AnimatedCodeButtonProps { - onClick: () => void; + onClick?: () => void; } diff --git a/src/components/Navigation/CodeButton/GlowingIconButton/index.tsx b/src/components/Navigation/CodeButton/GlowingIconButton/index.tsx index 43cbec7c2..41173daf4 100644 --- a/src/components/Navigation/CodeButton/GlowingIconButton/index.tsx +++ b/src/components/Navigation/CodeButton/GlowingIconButton/index.tsx @@ -1,14 +1,8 @@ -import { ForwardedRef, forwardRef } from "react"; import * as s from "./styles"; import { GlowingIconButtonProps } from "./types"; -export const GlowingIconButtonComponent = ( - props: GlowingIconButtonProps, - ref: ForwardedRef -) => ( - - +export const GlowingIconButton = (props: GlowingIconButtonProps) => ( + + ); - -export const GlowingIconButton = forwardRef(GlowingIconButtonComponent); diff --git a/src/components/Navigation/CodeButton/GlowingIconButton/types.ts b/src/components/Navigation/CodeButton/GlowingIconButton/types.ts index a4bbf8aee..ce3ad7321 100644 --- a/src/components/Navigation/CodeButton/GlowingIconButton/types.ts +++ b/src/components/Navigation/CodeButton/GlowingIconButton/types.ts @@ -2,7 +2,7 @@ type GlowingIconButtonType = "default" | "error"; export interface GlowingIconButtonProps { icon: React.ReactNode; - onClick: () => void; + onClick?: () => void; type?: GlowingIconButtonType; } diff --git a/src/components/Navigation/CodeButton/index.tsx b/src/components/Navigation/CodeButton/index.tsx index ceb115c6c..5c64570f8 100644 --- a/src/components/Navigation/CodeButton/index.tsx +++ b/src/components/Navigation/CodeButton/index.tsx @@ -1,4 +1,6 @@ -import { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, useEffect, useState } from "react"; +import { usePrevious } from "../../../hooks/usePrevious"; +import { isBoolean } from "../../../typeGuards/isBoolean"; import { ClockWithTicksIcon } from "../../common/icons/20px/ClockWithTicksIcon"; import { CodeIcon } from "../../common/icons/20px/CodeIcon"; import { OpenTelemetryLogoIcon } from "../../common/icons/20px/OpenTelemetryLogoIcon"; @@ -8,57 +10,87 @@ import * as s from "./styles"; import { CodeButtonProps } from "./types"; const CodeButtonComponent = ( - props: CodeButtonProps, - ref: ForwardedRef + { + hasData, + hasErrors, + hasObservability, + isDisabled, + isAlreadyAtScope, + onClick, + onMouseEnter, + onMouseLeave + }: CodeButtonProps, + ref: ForwardedRef ) => { - if (props.isDisabled || props.isAlreadyAtScope) { - return ( - } - icon={} - isDisabled={props.isDisabled} - onClick={props.onClick} - isActive={props.isAlreadyAtScope} - /> - ); - } + const [isHovered, setIsHovered] = useState(false); + const previousIsHovered = usePrevious(isHovered); - if (props.hasErrors) { - return ( - } - icon={} - onClick={props.onClick} - type={"error"} - /> - ); - } + const handleMouseEnter = () => { + setIsHovered(true); + }; - if (!props.hasObservability) { - return ( - } - icon={} - onClick={props.onClick} - /> - ); - } + const handleMouseLeave = () => { + setIsHovered(false); + }; - if (!props.hasData) { - return ( - } - icon={} - onClick={props.onClick} - /> - ); - } + useEffect(() => { + if (isBoolean(previousIsHovered) && previousIsHovered !== isHovered) { + if (isHovered) { + onMouseEnter(); + } else { + onMouseLeave(); + } + } + }, [onMouseEnter, onMouseLeave, previousIsHovered, isHovered]); + + const getButtonComponent = () => { + if (isDisabled || isAlreadyAtScope) { + return ( + } + isDisabled={isDisabled} + isActive={isAlreadyAtScope} + /> + ); + } + + if (hasErrors) { + return ( + } + type={"error"} + /> + ); + } + + if (!hasObservability) { + return ( + } + /> + ); + } + + if (!hasData) { + return ( + } + /> + ); + } + + return ; + }; return ( - } - onClick={props.onClick} - /> + + {getButtonComponent()} + ); }; diff --git a/src/components/Navigation/CodeButton/styles.ts b/src/components/Navigation/CodeButton/styles.ts index 59652ff5d..12b31f164 100644 --- a/src/components/Navigation/CodeButton/styles.ts +++ b/src/components/Navigation/CodeButton/styles.ts @@ -2,6 +2,10 @@ import styled from "styled-components"; import { IconButton } from "../common/IconButton"; import { ExtendedIconButtonProps } from "./types"; +export const Container = styled.div` + display: flex; +`; + export const ExtendedIconButton = styled(IconButton)` color: ${({ theme, isActive }) => isActive diff --git a/src/components/Navigation/CodeButton/types.ts b/src/components/Navigation/CodeButton/types.ts index 91677ad09..984cd77f9 100644 --- a/src/components/Navigation/CodeButton/types.ts +++ b/src/components/Navigation/CodeButton/types.ts @@ -11,4 +11,6 @@ export interface CodeButtonProps { hasData: boolean; isAlreadyAtScope: boolean; hasErrors: boolean; + onMouseEnter: () => void; + onMouseLeave: () => void; } diff --git a/src/components/Navigation/actions.ts b/src/components/Navigation/actions.ts index e9c0ee207..05127a0cb 100644 --- a/src/components/Navigation/actions.ts +++ b/src/components/Navigation/actions.ts @@ -14,5 +14,7 @@ export const actions = addPrefix(ACTION_PREFIX, { SET_AUTOFIX_MISSING_DEPENDENCY_RESULT: "SET_AUTOFIX_MISSING_DEPENDENCY_RESULT", ADD_ANNOTATION: "ADD_ANNOTATION", - SET_ADD_ANNOTATION_RESULT: "SET_ADD_ANNOTATION_RESULT" + SET_ADD_ANNOTATION_RESULT: "SET_ADD_ANNOTATION_RESULT", + HIGHLIGHT_METHOD_IN_EDITOR: "HIGHLIGHT_METHOD_IN_EDITOR", + CLEAR_HIGHLIGHTS_IN_EDITOR: "CLEAR_HIGHLIGHTS_IN_EDITOR" }); diff --git a/src/components/Navigation/common/IconButton/index.tsx b/src/components/Navigation/common/IconButton/index.tsx index 62620b1dd..86ff737bf 100644 --- a/src/components/Navigation/common/IconButton/index.tsx +++ b/src/components/Navigation/common/IconButton/index.tsx @@ -11,6 +11,8 @@ export const IconButtonComponent = ( className={props.className} onClick={props.onClick} disabled={props.isDisabled} + onMouseEnter={props.onMouseEnter} + onMouseLeave={props.onMouseLeave} > {props.icon} diff --git a/src/components/Navigation/common/IconButton/types.ts b/src/components/Navigation/common/IconButton/types.ts index 5375c0345..2c080176d 100644 --- a/src/components/Navigation/common/IconButton/types.ts +++ b/src/components/Navigation/common/IconButton/types.ts @@ -4,5 +4,7 @@ export interface IconButtonProps { icon: React.ReactNode; isDisabled?: boolean; className?: string; - onClick: () => void; + onClick?: () => void; + onMouseEnter?: () => void; + onMouseLeave?: () => void; } diff --git a/src/components/Navigation/index.tsx b/src/components/Navigation/index.tsx index 0d7ba56c2..216835548 100644 --- a/src/components/Navigation/index.tsx +++ b/src/components/Navigation/index.tsx @@ -32,6 +32,7 @@ import { ChangeScopePayload, ChangeViewPayload, CodeContext, + HighlightMethodInEditorPayload, OpenDashboardPayload, SetViewsPayload, TabData @@ -242,6 +243,23 @@ export const Navigation = () => { } }; + const handleCodeButtonMouseEnter = () => { + if (codeContext && codeContext.methodId) { + window.sendMessageToDigma({ + action: actions.HIGHLIGHT_METHOD_IN_EDITOR, + payload: { + methodId: codeContext.methodId + } + }); + } + }; + + const handleCodeButtonMouseLeave = () => { + window.sendMessageToDigma({ + action: actions.CLEAR_HIGHLIGHTS_IN_EDITOR + }); + }; + const handleEnvironmentChange = (environment: Environment) => { sendTrackingEvent(trackingEvents.ENVIRONMENT_SELECTED); setIsEnvironmentMenuOpen(false); @@ -393,6 +411,8 @@ export const Navigation = () => { onClick={handleCodeButtonClick} isAlreadyAtScope={isAlreadyAtScope(codeContext, config.scope)} hasErrors={false} + onMouseEnter={handleCodeButtonMouseEnter} + onMouseLeave={handleCodeButtonMouseLeave} /> @@ -404,6 +424,8 @@ export const Navigation = () => { onClick={handleCodeButtonClick} isAlreadyAtScope={isAlreadyAtScope(codeContext, config.scope)} hasErrors={false} + onMouseEnter={handleCodeButtonMouseEnter} + onMouseLeave={handleCodeButtonMouseLeave} /> )} diff --git a/src/components/Navigation/types.ts b/src/components/Navigation/types.ts index 9ab3f58f4..85f909bcf 100644 --- a/src/components/Navigation/types.ts +++ b/src/components/Navigation/types.ts @@ -49,6 +49,10 @@ export interface AddAnnotationPayload { methodId: string; } +export interface HighlightMethodInEditorPayload { + methodId: string; +} + export interface CodeContext { spans: { assets: { diff --git a/src/components/common/v3/Button/Button.stories.tsx b/src/components/common/v3/Button/Button.stories.tsx new file mode 100644 index 000000000..05fffdff2 --- /dev/null +++ b/src/components/common/v3/Button/Button.stories.tsx @@ -0,0 +1,38 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { Button } 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/v3/Button", + component: Button, + 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, + isDisabled: true + } +}; diff --git a/src/components/common/v3/Button/index.tsx b/src/components/common/v3/Button/index.tsx new file mode 100644 index 000000000..e8246497d --- /dev/null +++ b/src/components/common/v3/Button/index.tsx @@ -0,0 +1,22 @@ +import { ForwardedRef, forwardRef } from "react"; +import * as s from "./styles"; +import { ButtonProps } from "./types"; + +export const ButtonComponent = ( + props: ButtonProps, + ref: ForwardedRef +) => ( + + {props.icon && } + {typeof props.label === "string" && {props.label}} + +); +export const Button = forwardRef(ButtonComponent); diff --git a/src/components/common/v3/Button/styles.ts b/src/components/common/v3/Button/styles.ts new file mode 100644 index 000000000..a0dcb08de --- /dev/null +++ b/src/components/common/v3/Button/styles.ts @@ -0,0 +1,174 @@ +import styled, { css } from "styled-components"; +import { v3colors } from "../../App/v3colors"; +import { ButtonElementProps } from "./types"; + +export const Button = styled.button` + font-size: 13px; + border-radius: 4px; + display: flex; + align-items: center; + gap: 4px; + margin: 0; + cursor: pointer; + width: fit-content; + padding: 4px 8px; + border: none; + color: ${({ theme }) => theme.colors.v3.icon.primary}; + background: ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + return "none"; + case "secondary": + return theme.colors.v3.surface.primary; + case "primary": + default: + return v3colors.primary[400]; + } + }}; + + ${({ $type }) => + $type === "secondary" + ? css` + box-shadow: 0 2px 4px 0 rgb(0 0 0 / 13%); + ` + : ""}; + + span { + color: ${({ theme }) => theme.colors.v3.text.white}; + } + + &:disabled { + cursor: initial; + background: ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + return "none"; + case "secondary": + return theme.colors.v3.surface.primaryLight; + case "primary": + default: + return theme.colors.v3.surface.gray; + } + }}; + border: 1px solid + ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + return "none"; + case "secondary": + return theme.colors.v3.stroke.tertiary; + case "primary": + default: + return "none"; + } + }}; + color: ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + case "secondary": + return theme.colors.v3.icon.disabled; + case "primary": + default: + return theme.colors.v3.icon.tertiary; + } + }}; + + span { + color: ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + case "secondary": + return theme.colors.v3.text.disabled; + case "primary": + default: + return theme.colors.v3.text.tertiary; + } + }}; + } + } + + &:hover:enabled { + background: ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + return "none"; + case "secondary": + return theme.colors.v3.surface.brandDark; + case "primary": + default: + return v3colors.primary[300]; + } + }}; + border: 1px solid + ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + return "none"; + case "secondary": + return theme.colors.v3.stroke.primary; + case "primary": + default: + return v3colors.primary[300]; + } + }}; + + ${({ $type }) => + $type === "tertiary" + ? css` + color: ${({ theme }) => theme.colors.v3.icon.brandTertiary}; + ` + : ""}; + + span { + ${({ $type }) => + $type === "tertiary" + ? css` + color: ${({ theme }) => theme.colors.v3.text.link}; + ` + : ""}; + } + } + + &:focus:enabled, + &:active:enabled { + background: ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + return "none"; + case "secondary": + return theme.colors.v3.surface.primary; + case "primary": + default: + return v3colors.primary[500]; + } + }}; + border: 1px solid + ${({ theme, $type }) => { + switch ($type) { + case "tertiary": + return "none"; + case "secondary": + return theme.colors.v3.stroke.primary; + case "primary": + default: + return v3colors.primary[300]; + } + }}; + + ${({ $type }) => + $type === "tertiary" + ? css` + color: ${({ theme }) => theme.colors.v3.icon.brandTertiary}; + ` + : ""}; + + span { + ${({ $type }) => + $type === "tertiary" + ? css` + color: ${({ theme }) => theme.colors.v3.text.link}; + ` + : ""}; + } + } +`; diff --git a/src/components/common/v3/Button/types.ts b/src/components/common/v3/Button/types.ts new file mode 100644 index 000000000..437b78fa8 --- /dev/null +++ b/src/components/common/v3/Button/types.ts @@ -0,0 +1,19 @@ +import { ButtonHTMLAttributes } from "react"; +import { IconProps } from "../../icons/types"; + +export type ButtonType = "primary" | "secondary" | "tertiary"; + +export interface ButtonProps { + icon?: React.ComponentType; + label?: string; + onClick?: () => void; + isDisabled?: boolean; + buttonType?: ButtonType; + className?: string; + type?: ButtonHTMLAttributes["type"]; + form?: ButtonHTMLAttributes["form"]; +} + +export interface ButtonElementProps { + $type?: ButtonType; +} diff --git a/src/components/common/v3/KeyValueContainer/KeyValue/KeyValue.stories.tsx b/src/components/common/v3/KeyValueContainer/KeyValue/KeyValue.stories.tsx index e9f655b53..3a56b7ea6 100644 --- a/src/components/common/v3/KeyValueContainer/KeyValue/KeyValue.stories.tsx +++ b/src/components/common/v3/KeyValueContainer/KeyValue/KeyValue.stories.tsx @@ -16,7 +16,8 @@ export default meta; type Story = StoryObj; export const Default: Story = { - render: () => { - return Value; + args: { + label: "label", + children: "Value" } }; diff --git a/src/components/common/v3/KeyValueContainer/KeyValue/styles.ts b/src/components/common/v3/KeyValueContainer/KeyValue/styles.ts index 4820bb075..394f8d795 100644 --- a/src/components/common/v3/KeyValueContainer/KeyValue/styles.ts +++ b/src/components/common/v3/KeyValueContainer/KeyValue/styles.ts @@ -3,7 +3,6 @@ import styled from "styled-components"; export const Container = styled.div` display: flex; flex-direction: column; - align-items: flex-start; gap: 8px; `; diff --git a/src/components/common/v3/KeyValueContainer/KeyValue/types.ts b/src/components/common/v3/KeyValueContainer/KeyValue/types.ts index 2f5166c47..76b1890e6 100644 --- a/src/components/common/v3/KeyValueContainer/KeyValue/types.ts +++ b/src/components/common/v3/KeyValueContainer/KeyValue/types.ts @@ -1,6 +1,6 @@ import { ReactNode } from "react"; export interface KeyValueProps { - label: ReactNode | string; + label: ReactNode; children: ReactNode; } diff --git a/src/components/common/v3/KeyValueContainer/KeyValueContainer.stories.tsx b/src/components/common/v3/KeyValueContainer/KeyValueContainer.stories.tsx index 67a75fc10..8ce276336 100644 --- a/src/components/common/v3/KeyValueContainer/KeyValueContainer.stories.tsx +++ b/src/components/common/v3/KeyValueContainer/KeyValueContainer.stories.tsx @@ -16,12 +16,12 @@ export default meta; type Story = StoryObj; export const Default: Story = { - render: () => { - return ( - + args: { + children: ( + <> 31 5.01 sec - 223.42 sec - - ); + + ) } }; diff --git a/src/components/common/v3/KeyValueContainer/KeyValueContainer.tsx b/src/components/common/v3/KeyValueContainer/KeyValueContainer.tsx index e68347593..6c1ad2469 100644 --- a/src/components/common/v3/KeyValueContainer/KeyValueContainer.tsx +++ b/src/components/common/v3/KeyValueContainer/KeyValueContainer.tsx @@ -1,6 +1,6 @@ import * as s from "./styles"; import { KeyValueContainerProps } from "./types"; -export const KeyValueContainer = (props: KeyValueContainerProps) => { - return {props.children}; -}; +export const KeyValueContainer = (props: KeyValueContainerProps) => ( + {props.children} +); diff --git a/src/components/common/v3/KeyValueContainer/styles.ts b/src/components/common/v3/KeyValueContainer/styles.ts index 2fae92a6d..fb373131a 100644 --- a/src/components/common/v3/KeyValueContainer/styles.ts +++ b/src/components/common/v3/KeyValueContainer/styles.ts @@ -2,10 +2,8 @@ import styled from "styled-components"; export const Container = styled.div` display: flex; - align-items: flex-start; gap: 8px; justify-content: space-between; - align-self: stretch; `; export const Key = styled.div` diff --git a/src/components/common/v3/KeyValueContainer/types.ts b/src/components/common/v3/KeyValueContainer/types.ts index a51ff094c..c2e9a28e8 100644 --- a/src/components/common/v3/KeyValueContainer/types.ts +++ b/src/components/common/v3/KeyValueContainer/types.ts @@ -1,5 +1,5 @@ import { ReactNode } from "react"; export interface KeyValueContainerProps { - children: ReactNode[]; + children: ReactNode; } diff --git a/src/components/common/v3/Select/Select.stories.tsx b/src/components/common/v3/Select/Select.stories.tsx new file mode 100644 index 000000000..ff1fa6437 --- /dev/null +++ b/src/components/common/v3/Select/Select.stories.tsx @@ -0,0 +1,67 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import { Select } from "."; + +// More on how to set up stories at: https://storybook.js.org/docs/react/writing-stories/introduction +const meta: Meta = { + title: "Common/v3/Select", + component: Select, + 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; + +const mockedData = { + options: [ + { + label: "Very long long long long long long long long long long long name", + value: "item_1" + }, + { + label: "Item 2", + value: "item_2" + }, + { + label: "Item 3", + value: "item_3" + }, + { + label: "Item 4", + value: "item_4" + }, + { + label: "Item 5", + value: "item_5" + }, + { + label: "Item 6", + value: "item_6" + } + ] +}; + +export const Default: Story = { + args: { + options: mockedData.options, + value: mockedData.options[0].value + } +}; + +export const Empty: Story = { + args: { + options: [], + placeholder: "No items" + } +}; + +export const Disabled: Story = { + args: { + options: mockedData.options, + isDisabled: true + } +}; diff --git a/src/components/common/v3/Select/index.tsx b/src/components/common/v3/Select/index.tsx new file mode 100644 index 000000000..9d60d6ed8 --- /dev/null +++ b/src/components/common/v3/Select/index.tsx @@ -0,0 +1,57 @@ +import { useState } from "react"; +import { MenuList } from "../../../Navigation/common/MenuList"; +import { Popup } from "../../../Navigation/common/Popup"; +import { NewPopover } from "../../NewPopover"; +import { ChevronIcon } from "../../icons/16px/ChevronIcon"; +import { Direction } from "../../icons/types"; +import { Tooltip } from "../Tooltip"; +import * as s from "./styles"; +import { SelectOption, SelectProps } from "./types"; + +export const Select = (props: SelectProps) => { + const [isOpen, setIsOpen] = useState(false); + + const selectedOption = props.options.find((x) => x.value === props.value); + + const handleOptionClick = (option: SelectOption) => { + setIsOpen(false); + props.onChange(option.value); + }; + + return ( + + ({ + id: x.value, + label: x.label, + onClick: () => handleOptionClick(x) + }))} + /> + + } + onOpenChange={props.isDisabled ? undefined : setIsOpen} + isOpen={props.isDisabled ? false : isOpen} + placement={"bottom-start"} + > + + {selectedOption ? ( + + {selectedOption.label} + + ) : ( + props.placeholder + )} + + + + + + ); +}; diff --git a/src/components/common/v3/Select/styles.ts b/src/components/common/v3/Select/styles.ts new file mode 100644 index 000000000..d3a747f2d --- /dev/null +++ b/src/components/common/v3/Select/styles.ts @@ -0,0 +1,55 @@ +import styled from "styled-components"; +import { SelectBarProps } from "./types"; + +export const ChevronIconContainer = styled.div` + margin-left: auto; + display: flex; +`; + +export const SelectBar = styled.div` + overflow: hidden; + flex-grow: 1; + user-select: none; + padding: 5px 8px; + font-size: 14px; + gap: 4px; + border-radius: 4px; + display: flex; + align-items: center; + box-shadow: 0 0 5px 0 rgb(0 0 0 / 13%); + border: 1px solid + ${({ theme, $isOpen }) => + $isOpen + ? theme.colors.v3.stroke.primaryLight + : theme.colors.v3.stroke.primary}; + background: ${({ theme, $isDisabled, $isOpen }) => + $isDisabled + ? theme.colors.surface.primary + : $isOpen + ? theme.colors.v3.surface.primaryLight + : theme.colors.v3.surface.brandDark}; + color: ${({ theme, $isDisabled }) => + $isDisabled ? theme.colors.v3.text.secondary : theme.colors.v3.text.link}; + cursor: ${({ $isDisabled }) => ($isDisabled ? "initial" : "pointer")}; + + & ${ChevronIconContainer} { + color: ${({ theme, $isDisabled }) => + $isDisabled + ? theme.colors.v3.icon.disabled + : theme.colors.v3.icon.tertiary}; + } + + &:hover { + border: 1px solid + ${({ theme, $isDisabled }) => + $isDisabled + ? theme.colors.v3.stroke.primary + : theme.colors.v3.stroke.primaryLight}; + } +`; + +export const SelectedValue = styled.span` + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +`; diff --git a/src/components/common/v3/Select/types.ts b/src/components/common/v3/Select/types.ts new file mode 100644 index 000000000..fc2aca85c --- /dev/null +++ b/src/components/common/v3/Select/types.ts @@ -0,0 +1,17 @@ +export interface SelectOption { + label: string; + value: string; +} + +export interface SelectProps { + value?: string; + options: SelectOption[]; + placeholder?: string; + isDisabled?: boolean; + onChange: (value: string) => void; +} + +export interface SelectBarProps { + $isOpen: boolean; + $isDisabled?: boolean; +}