diff --git a/packages/synapse-react-client/src/components/EntityHeaderTable/EntityHeaderTable.tsx b/packages/synapse-react-client/src/components/EntityHeaderTable/EntityHeaderTable.tsx index 9d38deac15..ef33aee245 100644 --- a/packages/synapse-react-client/src/components/EntityHeaderTable/EntityHeaderTable.tsx +++ b/packages/synapse-react-client/src/components/EntityHeaderTable/EntityHeaderTable.tsx @@ -282,7 +282,7 @@ export const EntityHeaderTable = (props: EntityHeaderTableProps) => { }) setRefsInState(newRowRefs) } - ;`` + const isSelection = selectionCount > 0 const totalRowCount = data.length const filteredRowCount = table.getPrePaginationRowModel().rows.length diff --git a/packages/synapse-react-client/src/components/Markdown/MarkdownPopover.stories.tsx b/packages/synapse-react-client/src/components/Markdown/MarkdownPopover.stories.tsx index d50d2f66b8..b3d6b8bb06 100644 --- a/packages/synapse-react-client/src/components/Markdown/MarkdownPopover.stories.tsx +++ b/packages/synapse-react-client/src/components/Markdown/MarkdownPopover.stories.tsx @@ -1,21 +1,29 @@ import React from 'react' import { Meta, StoryObj } from '@storybook/react' -import { Button } from '@mui/material' import { MarkdownPopover, MarkdownPopoverProps } from './MarkdownPopover' +import { userEvent, within } from '@storybook/testing-library' +import { InfoTwoTone } from '@mui/icons-material' const meta = { title: 'Markdown/MarkdownPopover', + component: MarkdownPopover, args: { + children: , contentProps: { markdown: '' }, }, - render: args => ( - - - - ), -} satisfies Meta> + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/0oPm5lLSUva8kyfVNMS6FA/Sage-Style-%26-Component-Library?type=design&node-id=187-6607', + }, + }, + + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + const showPopoverButton = canvas.getByRole('button') + await userEvent.click(showPopoverButton) + }, +} satisfies Meta export default meta type Story = StoryObj @@ -30,7 +38,7 @@ export const WithAction: Story = { args: { contentProps: { markdown: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sed tellus lorem. In varius dui nec porttitor tristique. Suspendisse purus orci, dictum at lacus et, egestas commodo tortor. Mauris elementum, ligula in aliquet volutpat, sem arcu vestibulum enim, at scelerisque justo diam ut velit. Fusce iaculis tincidunt velit, vel dignissim dolor condimentum et. Sed ut nibh ac nunc facilisis facilisis.', + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. [Phasellus sed tellus lorem](https://synapse.org/). In varius dui nec porttitor tristique. Suspendisse purus orci, dictum at lacus et, egestas commodo tortor. Mauris elementum, ligula in aliquet volutpat, sem arcu vestibulum enim, at scelerisque justo diam ut velit. Fusce iaculis tincidunt velit, vel dignissim dolor condimentum et. Sed ut nibh ac nunc facilisis facilisis.', }, placement: 'right', actionButton: { @@ -51,14 +59,3 @@ export const WikiPage: Story = { placement: 'right', }, } - -export const NonButtonChild: Story = { - args: { - contentProps: { markdown: 'Tooltip on a div' }, - }, - render: args => ( - -
Click Me
-
- ), -} diff --git a/packages/synapse-react-client/src/components/Markdown/MarkdownPopover.tsx b/packages/synapse-react-client/src/components/Markdown/MarkdownPopover.tsx index ee3ae9edba..c1062289f0 100644 --- a/packages/synapse-react-client/src/components/Markdown/MarkdownPopover.tsx +++ b/packages/synapse-react-client/src/components/Markdown/MarkdownPopover.tsx @@ -1,17 +1,17 @@ -import React, { useState } from 'react' +import React, { useId } from 'react' import { - Button, Box, - TooltipProps, - tooltipClasses, + Button, ButtonProps, + tooltipClasses, + TooltipProps, + Typography, } from '@mui/material' import MarkdownSynapse, { MarkdownSynapseProps } from './MarkdownSynapse' -import { Typography } from '@mui/material' import LightTooltip from '../styled/LightTooltip' +import { atom, useAtom } from 'jotai' -export type MarkdownPopoverProps = { - children: JSX.Element +export type MarkdownPopoverProps = React.PropsWithChildren<{ contentProps: MarkdownSynapseProps sx?: TooltipProps['sx'] placement?: TooltipProps['placement'] @@ -25,7 +25,7 @@ export type MarkdownPopoverProps = { } maxWidth?: string minWidth?: string -} +}> const buttonBoxSx = { display: 'flex', @@ -37,6 +37,9 @@ const buttonBoxSx = { }, } +// Register a global atom to track which popover is open, to ensure only one is shown at any given time +const openMarkdownPopoverAtom = atom(null) + export const MarkdownPopover: React.FunctionComponent = ({ children, contentProps, @@ -47,7 +50,12 @@ export const MarkdownPopover: React.FunctionComponent = ({ maxWidth = '500px', minWidth = '300px', }: MarkdownPopoverProps) => { - const [show, setShow] = useState(false) + const id = useId() + const [openMarkdownPopoverId, setOpenMarkdownPopoverId] = useAtom( + openMarkdownPopoverAtom, + ) + + const show = openMarkdownPopoverId === id const content = ( @@ -62,7 +70,7 @@ export const MarkdownPopover: React.FunctionComponent = ({ onClick={() => { actionButton.onClick() if (actionButton.closePopoverOnClick) { - setShow(false) + setOpenMarkdownPopoverId(null) } }} > @@ -70,7 +78,10 @@ export const MarkdownPopover: React.FunctionComponent = ({ )} {showCloseButton && ( - )} @@ -82,7 +93,9 @@ export const MarkdownPopover: React.FunctionComponent = ({ setShow(!show)} + onClick={() => + setOpenMarkdownPopoverId(currentId => (currentId == id ? null : id)) + } open={show} sx={{ ...sx, diff --git a/packages/synapse-react-client/src/components/styled/LightTooltip.tsx b/packages/synapse-react-client/src/components/styled/LightTooltip.tsx index 3ce361d0bf..753b143657 100644 --- a/packages/synapse-react-client/src/components/styled/LightTooltip.tsx +++ b/packages/synapse-react-client/src/components/styled/LightTooltip.tsx @@ -1,5 +1,11 @@ import React from 'react' -import { Tooltip, TooltipProps, tooltipClasses, styled } from '@mui/material' +import { + Tooltip, + TooltipProps, + tooltipClasses, + styled, + linkClasses, +} from '@mui/material' import { StyledComponent } from '@emotion/styled' export const LightTooltip: StyledComponent = styled( @@ -8,7 +14,7 @@ export const LightTooltip: StyledComponent = styled( ), )(({ theme }) => ({ [`& .${tooltipClasses.tooltip}`]: { - backgroundColor: theme.palette.common.white, + backgroundColor: theme.palette.background.paper, color: theme.palette.grey[1000], boxShadow: theme.shadows[1], border: `1px solid ${theme.palette.grey[500]}`, @@ -18,7 +24,10 @@ export const LightTooltip: StyledComponent = styled( boxShadow: theme.shadows[1], border: `1px solid ${theme.palette.grey[500]}`, }, - color: theme.palette.common.white, + color: theme.palette.background.paper, + }, + [`& .${linkClasses.root}`]: { + color: theme.palette.primary.main, }, })) diff --git a/packages/synapse-react-client/src/theme/useTheme.tsx b/packages/synapse-react-client/src/theme/ThemeProvider.tsx similarity index 66% rename from packages/synapse-react-client/src/theme/useTheme.tsx rename to packages/synapse-react-client/src/theme/ThemeProvider.tsx index c74fcf8dfa..6ce024f14f 100644 --- a/packages/synapse-react-client/src/theme/useTheme.tsx +++ b/packages/synapse-react-client/src/theme/ThemeProvider.tsx @@ -1,16 +1,8 @@ import { createTheme, StyledEngineProvider, ThemeOptions } from '@mui/material' import { ThemeProvider as MuiThemeProvider } from '@mui/material/styles' -import { deepmerge } from '@mui/utils' import React, { useMemo } from 'react' import defaultMuiThemeOptions from './DefaultTheme' -import type { PartialDeep } from 'type-fest' - -export function mergeTheme( - themeOverrides: ThemeOptions | PartialDeep, -): ThemeOptions { - // TODO: Handle merging color palettes where an entire palette can be generated from a single base color. - return deepmerge(defaultMuiThemeOptions, themeOverrides) -} +import { mergeTheme } from './mergeTheme' export type ThemeProviderProps = React.PropsWithChildren<{ theme?: ThemeOptions diff --git a/packages/synapse-react-client/src/theme/index.ts b/packages/synapse-react-client/src/theme/index.ts index 0aa0afefac..dda49d6f1c 100644 --- a/packages/synapse-react-client/src/theme/index.ts +++ b/packages/synapse-react-client/src/theme/index.ts @@ -1,2 +1,3 @@ export { defaultMuiThemeOptions } from './DefaultTheme' -export * from './useTheme' +export * from './ThemeProvider' +export { mergeTheme } from './mergeTheme' diff --git a/packages/synapse-react-client/src/utils/hooks/useTheme.test.tsx b/packages/synapse-react-client/src/theme/mergeTheme.test.tsx similarity index 90% rename from packages/synapse-react-client/src/utils/hooks/useTheme.test.tsx rename to packages/synapse-react-client/src/theme/mergeTheme.test.tsx index 2c0c95e21c..0dc71dedab 100644 --- a/packages/synapse-react-client/src/utils/hooks/useTheme.test.tsx +++ b/packages/synapse-react-client/src/theme/mergeTheme.test.tsx @@ -1,7 +1,7 @@ -import { mergeTheme } from '../../theme/useTheme' import { ThemeOptions } from '@mui/material' -import defaultMuiThemeOptions from '../../theme/DefaultTheme' +import defaultMuiThemeOptions from './DefaultTheme' import { PartialDeep } from 'type-fest' +import { mergeTheme } from './mergeTheme' describe('Synapse Theme tests', () => { it('properly merges a custom theme with the default theme', () => { diff --git a/packages/synapse-react-client/src/theme/mergeTheme.ts b/packages/synapse-react-client/src/theme/mergeTheme.ts new file mode 100644 index 0000000000..536ca7a309 --- /dev/null +++ b/packages/synapse-react-client/src/theme/mergeTheme.ts @@ -0,0 +1,11 @@ +import { ThemeOptions } from '@mui/material' +import type { PartialDeep } from 'type-fest' +import { deepmerge } from '@mui/utils' +import defaultMuiThemeOptions from './DefaultTheme' + +export function mergeTheme( + themeOverrides: ThemeOptions | PartialDeep, +): ThemeOptions { + // TODO: Handle merging color palettes where an entire palette can be generated from a single base color. + return deepmerge(defaultMuiThemeOptions, themeOverrides) +} diff --git a/packages/synapse-react-client/src/utils/context/FullContextProvider.tsx b/packages/synapse-react-client/src/utils/context/FullContextProvider.tsx index 76bedd55ea..d5dffb4061 100644 --- a/packages/synapse-react-client/src/utils/context/FullContextProvider.tsx +++ b/packages/synapse-react-client/src/utils/context/FullContextProvider.tsx @@ -4,7 +4,7 @@ import { QueryClientConfig, QueryClientProvider, } from 'react-query' -import { ThemeProvider } from '../../theme/useTheme' +import { ThemeProvider } from '../../theme/ThemeProvider' import { ThemeOptions } from '@mui/material' import { SynapseContextProvider, SynapseContextType } from './SynapseContext' diff --git a/packages/synapse-react-client/stories/stubs/Tooltip/MuiTooltip.stories.tsx b/packages/synapse-react-client/stories/stubs/Tooltip/MuiTooltip.stories.tsx new file mode 100644 index 0000000000..69806ae5ae --- /dev/null +++ b/packages/synapse-react-client/stories/stubs/Tooltip/MuiTooltip.stories.tsx @@ -0,0 +1,38 @@ +import React from 'react' +import { Meta, StoryObj } from '@storybook/react' +import { Link } from '@mui/material' +import { InfoTwoTone } from '@mui/icons-material' +import { Tooltip } from './MuiTooltip' +import { userEvent, within } from '@storybook/testing-library' + +const meta = { + title: 'UI/MUI/Tooltip', + component: Tooltip, + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/0oPm5lLSUva8kyfVNMS6FA/Sage-Style-%26-Component-Library?node-id=187%3A6615', + }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement) + const tooltipAnchor = canvas.getByTestId('tooltipAnchor') + await userEvent.hover(tooltipAnchor) + }, +} satisfies Meta +export default meta + +type Story = StoryObj + +export const Demo: Story = { + name: 'Tooltip', + args: { + children: , + title: ( +

+ This is some text, and{' '} + here is a link. +

+ ), + }, +} diff --git a/packages/synapse-react-client/stories/stubs/Tooltip/MuiTooltip.tsx b/packages/synapse-react-client/stories/stubs/Tooltip/MuiTooltip.tsx new file mode 100644 index 0000000000..848c8c488a --- /dev/null +++ b/packages/synapse-react-client/stories/stubs/Tooltip/MuiTooltip.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import { + Tooltip as MuiTooltip, + TooltipProps as MuiTooltipProps, +} from '@mui/material' + +export interface TooltipProps extends MuiTooltipProps {} + +export const Tooltip = (props: TooltipProps) =>