From ac5b541013ea37f2f1a585ff38da7f9d4ec973b3 Mon Sep 17 00:00:00 2001 From: hpratt Date: Sat, 27 Aug 2022 18:12:28 -0400 Subject: [PATCH] add reactive search bar, deployment action --- .github/workflows/publish.yml | 32 ++++++++++ package.json | 2 +- src/components/AppBar/AppBar.tsx | 4 +- src/components/AppBar/DropDownMenuItem.tsx | 2 +- src/components/AppBar/OptionsMenuItem.tsx | 50 +++++++++++++++ src/components/AppBar/TabletAppBar.tsx | 41 ++++++++++++ src/components/AppBar/index.ts | 3 +- .../SearchBox/SearchBoxWithSelect.tsx | 11 +++- src/components/Typography/Typography.tsx | 2 +- src/index.ts | 2 +- stories/AppBar.stories.tsx | 10 ++- stories/DropDownMenu.stories.tsx | 12 +--- stories/SearchBox.stories.tsx | 63 +++++++++++++++---- stories/Typography.stories.tsx | 5 +- test/Typography.test.tsx | 10 +-- 15 files changed, 211 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/publish.yml create mode 100644 src/components/AppBar/OptionsMenuItem.tsx create mode 100644 src/components/AppBar/TabletAppBar.tsx diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..7cb742c --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,32 @@ +# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created +# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages + +name: Node.js Package + +on: + release: + types: [created] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + - run: yarn + + publish-npm: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + - run: yarn + - run: yarn publish --non-interactive + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/package.json b/package.json index 593bb87..958b62f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "0.4.1", + "version": "0.5.1", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/src/components/AppBar/AppBar.tsx b/src/components/AppBar/AppBar.tsx index f6eaf88..05ff477 100644 --- a/src/components/AppBar/AppBar.tsx +++ b/src/components/AppBar/AppBar.tsx @@ -30,7 +30,7 @@ export const StyledAppBar = styled(MUIAppBar)(() => ({ color: "#000000" })); -const PortalsMenuItem: React.FC<{ children?: React.ReactNode, onClick?: () => void }> = ({ children, onClick }) => ( +export const PortalsMenuItem: React.FC<{ children?: React.ReactNode, onClick?: () => void }> = ({ children, onClick }) => ( void }> = ({ ); -export const AppBar: React.FC = props => ( +const AppBar: React.FC = props => ( diff --git a/src/components/AppBar/DropDownMenuItem.tsx b/src/components/AppBar/DropDownMenuItem.tsx index fc44d4d..568c09e 100644 --- a/src/components/AppBar/DropDownMenuItem.tsx +++ b/src/components/AppBar/DropDownMenuItem.tsx @@ -56,7 +56,7 @@ const DropDownMenuItem: React.FC = props => { {...TransitionProps} style={{ transformOrigin: placement === 'bottom-start' ? 'left top' : 'left bottom' }} > - + setOpen(false)}> {props.menu} diff --git a/src/components/AppBar/OptionsMenuItem.tsx b/src/components/AppBar/OptionsMenuItem.tsx new file mode 100644 index 0000000..abcb16d --- /dev/null +++ b/src/components/AppBar/OptionsMenuItem.tsx @@ -0,0 +1,50 @@ +/** + * OptionsMenuItem.tsx: a PsychSCREEN app bar menu item with a hamburger icon and a pop-out sub menu for options. + */ + + import React, { useState } from 'react'; + import { ClickAwayListener, Grow, MenuList, Popper } from '@mui/material'; +import MenuIcon from '@mui/icons-material/Menu'; + +import { DropDownMenu } from '../DropDownMenu'; +import { MenuItemProps } from './DropDownMenuItem'; + +const OptionsMenuItem: React.FC = props => { + const anchorRef = React.useRef(null); + const [ open, setOpen ] = useState(false); + return ( + <> +
+ setOpen(true)} + /> +
+ { anchorRef.current && ( + + { ({ TransitionProps, placement }) => ( + + + setOpen(false)}> + + {props.menu} + + + + + )} + + )} + + ); +} +export default OptionsMenuItem; diff --git a/src/components/AppBar/TabletAppBar.tsx b/src/components/AppBar/TabletAppBar.tsx new file mode 100644 index 0000000..b501282 --- /dev/null +++ b/src/components/AppBar/TabletAppBar.tsx @@ -0,0 +1,41 @@ +/** + * TabletAppBar.tsx: PsychSCREEN app bar for tablets and mobile devices. + */ + +import React from 'react'; +import { AppBarProps, StyledAppBar, PortalsMenuItem as OptionsMenuItem } from './AppBar'; +import { Box, Toolbar } from '@mui/material'; + +import MenuItem from './MenuItem'; +import OptionsMenu from './OptionsMenuItem'; + +export type TabletAppBarProps = AppBarProps & { title?: string }; + +const PortalsMenu: React.FC<{ onItemClicked?: (index: number) => void }> = ({ onItemClicked }) => ( + <> + onItemClicked && onItemClicked(-1)}>About + onItemClicked && onItemClicked(0)}>Disease/Trait Portal + onItemClicked && onItemClicked(1)}>Gene/bCRE Portal + onItemClicked && onItemClicked(2)}>SNP/QTL Portal + onItemClicked && onItemClicked(3)}>Single-Cell Portal + +); + +const TabletAppBar: React.FC = props => ( + + + + } + > + Portals + + + { props.title || "" } + + + + +); +export default TabletAppBar; diff --git a/src/components/AppBar/index.ts b/src/components/AppBar/index.ts index 564338e..7d88f07 100644 --- a/src/components/AppBar/index.ts +++ b/src/components/AppBar/index.ts @@ -1,2 +1,3 @@ import AppBar, { AppBarProps } from './AppBar'; -export { AppBar, AppBarProps }; +import TabletAppBar, { TabletAppBarProps } from './TabletAppBar'; +export { AppBar, AppBarProps, TabletAppBar, TabletAppBarProps }; diff --git a/src/components/SearchBox/SearchBoxWithSelect.tsx b/src/components/SearchBox/SearchBoxWithSelect.tsx index c4746ef..0ebce58 100644 --- a/src/components/SearchBox/SearchBoxWithSelect.tsx +++ b/src/components/SearchBox/SearchBoxWithSelect.tsx @@ -1,5 +1,5 @@ import { MenuItem } from '@mui/material'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { Select } from '../Select'; import SearchBox, { SearchBoxProps } from './SearchBox'; @@ -13,10 +13,16 @@ export type SearchBoxWithSelectProps = SearchBoxProps & { selectOptions: SearchBoxWithSelectOption[]; onSelectChange?: (option: SearchBoxWithSelectOption) => void; onSearchChange?: (value: string) => void; + reactiveThreshold?: number; + reactiveWidth?: number; + containerWidth?: number; }; const SearchBoxWithSelect: React.FC = props => { const [ option, setOption ] = useState(props.selectOptions[0]); + const belowThreshold = useMemo( () => ( + props.reactiveThreshold && (props.containerWidth || 0) < props.reactiveThreshold + ), [ props.reactiveThreshold, props.containerWidth ]); return ( <> + { belowThreshold ?
: null } props.onSearchChange && props.onSearchChange(e.target.value)} helperText={option.helperText} + width={belowThreshold ? props.reactiveWidth : undefined} {...props} /> diff --git a/src/components/Typography/Typography.tsx b/src/components/Typography/Typography.tsx index b65cf3b..007e98b 100644 --- a/src/components/Typography/Typography.tsx +++ b/src/components/Typography/Typography.tsx @@ -6,7 +6,7 @@ import styled from '@emotion/styled'; import { Typography as MUITypography, TypographyProps as MUITypographyProps } from '@mui/material'; import { PSYCHSCREEN_DEFAULT_FONT_FAMILY } from '../../constants/theme'; -type TypographyType = 'body' | 'title' | 'headline' | 'display' | 'label'; +export type TypographyType = 'body' | 'title' | 'headline' | 'display' | 'label'; type TypographySize = 'large' | 'medium' | 'small'; type TypographyPropertyDictionary = Map>; diff --git a/src/index.ts b/src/index.ts index 64bb8f7..d6ecca2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export { AppBar, AppBarProps } from './components/AppBar'; +export { AppBar, AppBarProps, TabletAppBar, TabletAppBarProps } from './components/AppBar'; export { Button, ButtonProps } from './components/Button'; export { Typography, TypographyProps } from './components/Typography'; export { SearchBox, SearchBoxProps, SearchBoxWithSelect, SearchBoxWithSelectProps } from './components/SearchBox'; diff --git a/stories/AppBar.stories.tsx b/stories/AppBar.stories.tsx index 8e5568a..daf7110 100644 --- a/stories/AppBar.stories.tsx +++ b/stories/AppBar.stories.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Meta, Story } from '@storybook/react'; -import { AppBar, AppBarProps, PSYCHSCREEN_DEFAULT_THEME } from '../src'; +import { AppBar, TabletAppBar, TabletAppBarProps, PSYCHSCREEN_DEFAULT_THEME } from '../src'; import "../src/App.css"; import { ThemeProvider } from '@emotion/react'; @@ -21,7 +21,11 @@ const meta: Meta = { export default meta; -const Template: Story = args => ( +const Template: Story = args => args.tablet ? ( + + + +) : ( @@ -30,5 +34,7 @@ const Template: Story = args => ( // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test // https://storybook.js.org/docs/react/workflows/unit-testing export const Default = Template.bind({}); +export const Tablet = Template.bind({}); Default.args = {}; +Tablet.args = { tablet: true, title: "Tablet App Bar" }; diff --git a/stories/DropDownMenu.stories.tsx b/stories/DropDownMenu.stories.tsx index fd8ea65..9d17c50 100644 --- a/stories/DropDownMenu.stories.tsx +++ b/stories/DropDownMenu.stories.tsx @@ -33,14 +33,6 @@ const Template: Story = args => ( // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test // https://storybook.js.org/docs/react/workflows/unit-testing -export const Body = Template.bind({}); -export const Title = Template.bind({}); -export const Display = Template.bind({}); -export const Label = Template.bind({}); -export const Headline = Template.bind({}); +export const Default = Template.bind({}); -Body.args = { type: 'body' }; -Headline.args = { type: 'headline' }; -Display.args = { type: 'display' }; -Label.args = { type: 'label' }; -Title.args = { type: 'title' }; +Default.args = {}; diff --git a/stories/SearchBox.stories.tsx b/stories/SearchBox.stories.tsx index b4a8614..6ccbe11 100644 --- a/stories/SearchBox.stories.tsx +++ b/stories/SearchBox.stories.tsx @@ -1,8 +1,27 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Meta, Story } from '@storybook/react'; import { SearchBox, SearchBoxProps } from '../src'; import "../src/App.css"; import { SearchBoxWithSelect } from '../src/components/SearchBox'; +import { Grid } from '@mui/material'; + +function getWindowDimensions() { + const { innerWidth: width, innerHeight: height } = window; + return { + width, + height + }; +} + +function useViewportSize() { + const [ windowDimensions, setWindowDimensions ] = useState(getWindowDimensions()); + useEffect(() => { + const handleResize = () => { setWindowDimensions(getWindowDimensions()); }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + return windowDimensions; +} const meta: Meta = { title: 'SearchBox', @@ -27,16 +46,38 @@ const SELECT_OPTIONS = [ { name: "SNP/QTL", value: "SNP", helperText: "e.g. rs2836883, rs7690700" } ] -const Template: Story = args => args.withSelect ? ( - -) : ( - -); +const Template: Story = args => { + const { width } = useViewportSize(); + return args.withSelect ? ( + <> + + + Non-Reactive
+ +
+ + Reactive (at width <200px; current width is {width / 2})
+ +
+
+ + ) : ( + + ); +}; // By passing using the Args format for exported stories, you can control the props for a component for reuse in a test // https://storybook.js.org/docs/react/workflows/unit-testing diff --git a/stories/Typography.stories.tsx b/stories/Typography.stories.tsx index d089a94..ea77b2f 100644 --- a/stories/Typography.stories.tsx +++ b/stories/Typography.stories.tsx @@ -2,8 +2,9 @@ import React from 'react'; import { Meta, Story } from '@storybook/react'; import { ThemeProvider } from '@emotion/react'; -import { Typography, TypographyProps, PSYCHSCREEN_DEFAULT_THEME } from '../src'; +import { Typography, PSYCHSCREEN_DEFAULT_THEME } from '../src'; import "../src/App.css"; +import { TypographyType } from '../src/components/Typography/Typography'; const meta: Meta = { title: 'Typography', @@ -22,7 +23,7 @@ const meta: Meta = { export default meta; -const Template: Story = args => ( +const Template: Story<{ type: TypographyType }> = args => (

Typography for {args.type} elements: diff --git a/test/Typography.test.tsx b/test/Typography.test.tsx index 03b4f48..fc6c58f 100644 --- a/test/Typography.test.tsx +++ b/test/Typography.test.tsx @@ -6,31 +6,31 @@ describe('Body', () => { it('renders body typography without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(, div); + ReactDOM.render(, div); ReactDOM.unmountComponentAtNode(div); }); it('renders title typography without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(, div); + ReactDOM.render(<Title type="title" />, div); ReactDOM.unmountComponentAtNode(div); }); it('renders display typography without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(<Display />, div); + ReactDOM.render(<Display type="display" />, div); ReactDOM.unmountComponentAtNode(div); }); it('renders headline typography without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(<Headline />, div); + ReactDOM.render(<Headline type="headline" />, div); ReactDOM.unmountComponentAtNode(div); }); it('renders label typography without crashing', () => { const div = document.createElement('div'); - ReactDOM.render(<Label />, div); + ReactDOM.render(<Label type="label" />, div); ReactDOM.unmountComponentAtNode(div); });